廖雪峰Python教程和慕课网视频笔记
数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。
我们会讨论多重继承、定制类、元类等概念。
1.使用_slots_
先说下MethodType的使用:
参考文章:
https://blog.csdn.net/yuanyangsdo/article/details/60776612
https://blog.csdn.net/simple1_6/article/details/79309287
在动态语言中,有一种方法可以使类或者实例在没有方法或者没有想要的方法时,动态的加上一个方法。使用的就是 MethodType()。
1.绑定方法到实例中
class Student (object): #先建立一个类
pass
def set_age(self,age): #一个即将被绑定的方法
self.age=age
s=Student() #创建一个实例
from types import MethodType
s.set_age=MethodType(set_age,s) #动态绑定方法:set_age到s
s.set_age=22 #使用动态绑定的方法
print(s.set_age) #输出22
s.set_age=MethodType(set_age,s)
s.set_age(23)
print(s.age) #输出23
2.绑定到类上
class Student (object): #先建立一个类
pass
def set_age(self,age): #一个即将被绑定的方法
self.age=age
from types import MethodType
Student.set_age=MethodType(set_age,Student)
Student.set_age=22
print(Student.set_age) #输出22
Student.set_age=MethodType(set_age,Student)
Student.set_age(23)
print(Student.age) #输出23
s1=Student()
s1.age #输出23
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:
class Student(object):
pass
然后,尝试给实例绑定一个属性:
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael
还可以尝试给实例绑定一个方法:
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:
>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
为了给所有实例都绑定方法,可以给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
给class绑定方法后,所有实例均可调用:
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
限制实例的属性:__slots__
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
然后,我们试试:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
由于’score’没有被放到__slots__
中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
2.使用@property
一、最初的版本
# 考察Student类
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
# 修改一个Student的score的属性时,可以这么写
s = Student('Bob',59)
s.score = 60
显然,直接给属性赋值无法检查分数的有效性。
二、Set/Get的使用
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
这样一来,s.set_score(1000) 就会报错。
这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
三、@property的使用
但是写 s.get_score()
和 s.set_score()
没有直接写 s.score
来得直接。
有没有两全其美的方法?—-有。
因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。
现在,就可以像使用属性一样设置score了:
>>> s = Student('Bob', 59)
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> print s.score # OK,实际转化为s.get_score()
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score
说明对 score 赋值实际调用的是 set方法。
参考文章:https://blog.csdn.net/u013205877/article/details/77804137
任务
如果没有定义set方法,就不能对“属性”赋值,这时,就可以创建一个只读“属性”。(用 @property 修饰 grade 的 get 方法即可实现只读属性。)
请给Student类加一个grade属性,根据 score 计算 A(>=80)、B、C(<60)。
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
@property
def grade(self):
if self.score < 60:
return 'C'
if self.score < 80:
return 'B'
return 'A'
s = Student('Bob', 59)
print s.grade
s.score = 60
print s.grade
s.score = 99
print s.grade
小结:@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
3.多重继承
除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。
class A(object):
def __init__(self, a):
print 'init A...'
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print 'init B...'
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print 'init C...'
class D(B, C):
def __init__(self, a):
super(D, self).__init__(a)
print 'init D...'
像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。多重继承通过 super()调用__init__()
方法时,A 虽然被继承了两次,但__init__()
只调用一次:
d = D('d')
init A...
init C...
init B...
init D...
根据上面的代码有:D最后出现可理解,因为构造结束后才调用print,A最先出现也可理解,因为无论BC谁先执行super都会牵扯到A的构造,而BC参考问答,最右边的父类优先继承,也就是说C优先构造结束并print,C先于B也可以解释,而关键之处在于:为什么A的构造只被调用了一次?按照逻辑上来说A的构造理应调用两次。因为多继承类是通过mro的方式保证各个父类的函数被逐一调用,保证每个父类函数只调用一次(如果每个类都使用super)。
附注:当多继承的类中有相同的方法实现时,同级的以取左原则,下级方法重写上级方法。
参考文章:https://blog.csdn.net/johnsonguo/article/details/585193
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
Mixin:
多继承这种设计通常称之为Mixin。
为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixin和FlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:
class Dog(Mammal, RunnableMixin, CarnivorousMixin):
pass
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
Python多继承之拓扑排序:
可以参考文章:https://kevinguo.me/2018/01/19/python-topological-sorting/
多重继承遵循拓扑排序原则。
什么是拓扑排序:
- 从DAG途中选择一个没有前驱(即入度为0)的顶点并输出
- 从图中删除该顶点和所有以它为起点的有向边。
- 重复1和2直到当前DAG图为空或当前途中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
python多重继承:
- 把继承关系先构成一张图
- 利用拓扑排序的方法输出拓扑顺序,并列关系时遵循取最左原则
- python继承顺序遵循C3算法,只要在一个地方找到了所需的内容,就不再继续查找
Python普通继承和super继承方式:
当存在继承关系的时候,有时候需要在子类中调用父类的方法,此时最简单的方法是把对象调用转换成类调用,需要注意的是这时self参数需要显式传递,例如:
class FooParent:
def bar(self, message):
print(message)
class FooChild(FooParent):
def bar(self, message):
FooParent.bar(self, message)
FooChild().bar("Hello, World.")
> Hello, World.
这样做有一些缺点,比如说如果修改了父类名称,那么在子类中会涉及多处修改,另外,Python是允许多继承的语言,如上所示的方法在多继承时就需要重复写多次,显得累赘。为了解决这些问题,Python引入了super()机制,例子代码如下:
class FooParent:
def bar(self, message):
print(message)
class FooChild(FooParent):
def bar(self, message):
super(FooChild, self).bar(message)
FooChild().bar("Hello, World.")
> Hello, World.
表面上看 super(FooChild, self).bar(message)方法和FooParent.bar(self, message)方法的结果是一致的,实际上这两种方法的内部处理机制大大不同,当涉及多继承情况时,就会表现出明显的差异来,直接给例子:
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
A.__init__(self)
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
A.__init__(self)
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
A.__init__(self)
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
B.__init__(self)
C.__init__(self)
D.__init__(self)
print("Leave E")
E()
结果:
Enter E
Enter B
Enter A
Leave A
Leave B
Enter C
Enter A
Leave A
Leave C
Enter D
Enter A
Leave A
Leave D
Leave E
执行顺序很好理解,唯一需要注意的是公共父类A被执行了多次。
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
super(B, self).__init__()
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
super(C, self).__init__()
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
super(D, self).__init__()
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
super(E, self).__init__()
print("Leave E")
E()
结果:
Enter E
Enter B
Enter C
Enter D
Enter A
Leave A
Leave D
Leave C
Leave B
Leave E
在super机制里可以保证公共父类仅被执行一次,至于执行的顺序,是按照mro进行的(E.__mro__)
。