[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。
36. 面向对象编程
- 面向过程方式
lijun = {"name":"lijun","score":34}
wangling = {"name":"wangling","score":30}
def print_score(std):
print("%s:%s" % (std["name"],std["score"]))
print_score(lijun)
lijun:34
- 面向对象方式
class Student(object):
def __init__(self,name,score):
self.name = name
self.score = score
def print_score(self):
print("%s:%s" % (self.name,self.score))
lijun = Student("lijun",34)
wangling = Student("wangling",30)
lijun.print_score()
lijun:34
- 小结:
数据封装,继承和多态,是面向对象的主要特征,python中的这些概念应该与java或是或是C++中相同。 从上面的简单示例可以看出面向对象的一些特点:
- 将属性和行为抽象成了一个class,通过class可以生成具有相同属性和行为的对象。
- 对象执行自身的行为函数,实现特定的功能。
37. 类和实例
- 如下是对象在内存的地址
wangling
<__main__.Student at 0x3e8f3b0>
lijun
<__main__.Student at 0x3e8f350>
- Student是个类
Student
__main__.Student
本章介绍了类和实例的基本概念,与其他语言相通,省略。
38. 访问限制
上面的Student类中,有属性 name 和 score,都是公有属性,即可以通过对象直接访问该属性,如下可以直接修改。
lijun.score = 18
但是这样是非常不安全的,通过在属性前面添加__
可以使变量变成私有属性,如下:
class Student2(object):
def __init__(self,name,score):
self.__name = name
self.__score = score
def print_score(self):
print("%s:%s" % (self.__name,self.__score))
lijun2 = Student2("lijun",34)
wangling2 = Student2("wangling",30)
添加了上面私有属性标识后,就无法直接通过对象访问该变量了,会出现如下错误:
lijun2.score
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-14-7fe4493c4cd9> in <module>()
----> 1 lijun2.score
AttributeError: 'Student2' object has no attribute 'score'
那如果我想修改和访问该变量的话,该怎么办呢?这里的解决方法就是添加setter和getter,如下:
def set_score(self,score):
self.__score = score
def get_score(self):
return self.__score
看了上面的例子,可能觉得这是多此一举,但是通过setter和getter可以对对象的属性进行预处理,比如设定score的时候,能先检查score是否在有效范围:
class Student(object):
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
39. 继承和多态
1.继承
class Animal(object):
def run(self):
print("Animal is running!")
class Dog(Animal):
def wangwang(self):
pass
def run(self):
print("Dog is running!")
class Cat(Animal):
def run(self):
print("Cat is running!")
utane = Cat()
utasuke = Dog()
utane.run()
utasuke.run()
Cat is running!
Dog is running!
上面的类中,Animal是基类或父类,Dog和Cat继承于它,是其子类,子类中重写了run方法,所以子类的对象在调用run方法时,使用的是被重写后的run方法。
如果Dog类中没有run方法的话,则Dog的对象会调用父类Animal中的run方法。
2.多态
2.1 进入多态之前,先看看数据类型。
- 通过子类Cat和Dog创建的对象,其类型都是对应子类
isinstance(utane,Cat)
True
isinstance(utasuke,Dog)
True
- 因为子类Cat和Dog继承于Animal,那么子类的对象,同时也是父类的类型
isinstance(utane,Animal)
True
- 反之父类的对象,不能是子类的类型
animalA = Animal()
isinstance(animalA,Animal)
True
isinstance(animalA,Dog)
False
2.2 多态的用处
- 定义一个功能函数
def runtwice(animal):
animal.run()
animal.run()
runtwice(Animal())
Animal is running!
Animal is running!
runtwice(Dog())
Dog is running!
Dog is running!
runtwice(Cat())
Cat is running!
Cat is running!
class Monkey(Animal):
def run(self):
print("Monkey is running!")
runtwice(Monkey())
Monkey is running!
Monkey is running!
对于runtwice方法,只需要知道接收的参数是Animal类型,无需知道其具体的子类型,在运行的时候确定该对象的确切类型。这就是多态的真正威力,即调用方只管调用,不管细节。 比如,新增Animal的子类Monkey,但是不需要修改依赖Animal类的方法runtwice。
比如,有一个电器启动方法,传入参数是电器对象,方法中的处理分别是开启电源,调整显示,…等一系列操作的处理函数。 但是各个具体的电器的电源开启方法,以及显示调整方法是不同的,这些实现的细节在子类中完成,这个电器启动方法相当于接口,只管调用。 如果新增了一类电器,其启动流程符合这个接口函数,那么该新增电器也可以使用这个接口。
对多态的理解还很浅显,在下一阶段阅读python进阶 的时候再深入。
3. 静态语言与动态语言
上面的runTwice方法接收Animal类型的参数,调用Animal类或子类的run方法。
-
在动态语言中,编译时会有类型检查,传入的对象必须是Animal类或其子类,否则无法调用run方法。
-
在静态语言中,传入的对象并不一定要求它是Animal类或子类,只需要其类有run方法就可以调用。
40. 获取对象信息
使用type()
type(123)
int
type("str")
str
type(None)
NoneType
type(abs)
builtin_function_or_method
type(animalA)
__main__.Animal
- 判断函数,可以使用types模块中定义的常量
import types
def fn():
pass
type(fn) == types.FunctionType
True
type(abs) == types.BuiltinFunctionType
True
type(lambda x:x) == types.LambdaType
True
type(x for x in range(10)) == types.GeneratorType
True
使用instance()
class DogA(Dog):
def wangwangA(self):
pass
def run(self):
print("DogA is running!")
a = Animal()
b = Dog()
c = DogA()
isinstance(c,DogA)
True
isinstance(c,Dog)
True
isinstance(c,Animal)
True
isinstance(b,Dog) and isinstance(b,Animal)
True
isinstance(b,DogA)
False
同样,普通数据类型,也可以使用isinstance判断
isinstance([1,2,3],(list,tuple))
True
isinstance((1,2,3),(list,tuple))
True
使用dir()
可以使用 dir()函数,它返回 一个包含字符串的 list,比如,获得一个 str 对象的所有属性和方法:
dir("abc")
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
类似__xxx__的属性和方法在 Python 中都是有特殊用途的,比如__len__ 方法返回长度。在 Python 中,如果你调用 len()函数试图获取一个对象的长度,实际上,在 len()函数内部,它自动去调用该对象的__len__() 方法,所以,下面的代码是等价的:
len("abc")
3
"abc".__len__()
3
我们自己写的类,如果也想用 len(myObj)的话,就自己写一个__len__() 方法
class DogB(Dog):
def __init__(self,age):
self.age = age
def wangwangB(self):
pass
def __len__(self):
print("DogB len")
return 999
def run(self):
print("DogA is running!")
len(DogB())
DogB len
999
关于getattr()、setattr()以及 hasattr()
dogb = DogB(2)
hasattr(dogb,"age")
True
setattr(dogb,"age",20)
getattr(dogb,"age")
20
如果试图获取不存在的属性,会抛出AttributeError 的错误:
getattr(dogb, 'z')
AttributeError Traceback (most recent call last)
<ipython-input-109-844d9bbaa80b> in <module>()
----> 1 getattr(dogb, 'z')
AttributeError: 'DogB' object has no attribute 'z'
getattr(dogb, 'z', 404)
404
获取对象的方法,与上面获取对象的属性的方式一致。
小结
如下是一个实际代码中使用hasasttr()的实例:
def readImage(fp):
if hasattr(fp,"read"):
return readData(fp)
return None
假设我们希望从文件流fp中读取对象,首先要判断fp对象是否有read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。 这时能使用hasattr进行判断。
另外,在python这类动态语言中,根据鸭子类型,有read()方法,不代表fp对象就是一个文件流,也可能是网络流,也可能是内存中的一个字节流,但只要read方法返回的是有效的图像数据就不影响图像的功能。
如同上面的例子,先判断这个电器对象是否有poweron方法,有的话就调用启动方法。