python类的继承原理
一、类的继承顺序
class A(object):
def test(self):
print('from A')
pass
class B(A):
# def test(self):
# print('from B')
pass
class C(A):
# def test(self):
# print('from C')
pass
class D(A):
# def test(self):
# print('from D')
pass
class E(B):
# def test(self):
# print('from E')
pass
class F(C):
# def test(self):
# print('from F')
pass
class G(D):
# def test(self):
# print('from G')
pass
class H(E,F,G):
# def test(self):
# print('from H')
pass
h=H()
# h.test=1
# print h.__dict__
#新式类的在这中继承结构下,属性的查找关系
# h.test()
# H->E->B->F->C-G-D-A 广度优先
#mro
print(H.mro())
二、类的继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
三、子类调用父类的方法
两种方式 :一个是直接指明道姓的调用父类的函数属性还有一个是调用父类的__init__的功能,实际上用的是绑定方法。
class People:
# def __init__(self,name,age,sex):
# self.name=name
# self.age=age
# self.sex=sex
# def foo(self):
# print('from parent')
#
# class Teacher(People):
# def __init__(self,name,age,sex,salary,level):
# # People.__init__(self,name,age,sex) #指名道姓地调用People类的__init__函数
#
# #在python3中
# super().__init__(name,age,sex) #调用父类的__init__的功能,实际上用的是绑定方法
#
# #在python2中
# # super(Teacher,self).__init__(name,age,sex)
#
#
# self.salary=salary
# self.level=level
# def foo(self):
# super().foo()
# print('from child')
#
#
# t=Teacher('egon',18,'male',3000,10)
# # print(t.name,t.age,t.sex,t.salary,t.level)
# t.foo()
注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表
四封装
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
五、类的特性(property)
1 什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
2 为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
上代码:
class People:
def __init__(self,name,age,sex,height,weight,permission=False):
self.__name=name
self.__age=age
self.__sex=sex
self.__height=height
self.__weight=weight
self.permission=permission
@property
def name(self):
return self.__name
@name.setter
def name(self,val):
if not isinstance(val,str):
raise TypeError('名字必须为字符串!')
self.__name=val
@name.deleter
def name(self):
if not self.permission:
raise PermissionError('权限不够!')
del self.__name
@property
def bmi(self):
return self.__weight/(self.__height**2)
def tell_info(self):
print('''
---------%s info------------
Name:%s
Age:%s
Sex:%s
Height:%sM
Weight:%sKG
---------------------------------
'''%(self.__name,self.__name,self.__age,self.__sex,self.__height,self.__weight))
niubin=People('niubin',20,'male',1.70,68)
niubin.tell_info()
print(niubin.name)
print(niubin.bmi)