继承实现原理

python中的类可以同时继承多个父类,继承的顺序有两种:深度优先和广度优先。

一般来讲,经典类在多继承的情况下会按照深度优先的方式查找,新式类会按照广度优先的方式查找

示例解析:

没有共同头部父类的类型

 class E:
def test(self):
print('from E')
# pass
class F:
def test(self):
print('from F')
# pass class C:
def test(self):
print('from C')
# pass class B(C):
def test(self):
print('from B')
# pass class D(E):
def test(self):
print('from D')
# pass
class A(B,D,F):
def test(self):
print('from A')
# pass
obj=A()
obj.test()

在这种模型下,新式类和经典类的继承顺序都一样。

调用obj.test(),首先找boj对象的__dict__字典,然后找生成类A的__dict__字典,如果这两个都没有,会按照以下顺序进行查找,找到为止:

ClassA->ClassB->ClassC->ClassD->ClassE->ClassF

如果都找不到,抛出异常错误。

有共同头部父类的类型

 class D(object):
def test(self):
print('from D')
# pass
class C(D):
def test(self):
print('from C')
# pass
class B(C):
def test(self):
print('from B')
# pass
class F(D):
def test(self):
print('from F')
# pass
class E(F):
def test(self):
print('from E')
# pass
class H(D):
def test(self):
print('from H')
# pass
class G(H):
def test(self):
print('from G')
# pass class A(B,E,G):
def test(self):
print('from A')
# pass obj=A()
obj.test()
print(A.mro())

在这种模型下,新式类和经典类查找继承顺序不同。

新式类使用的是广度优先的方式,调用obj.test(),首先找boj对象的__dict__字典,然后找生成类A的__dict__字典,如果这两个都没有,会按照以下顺序进行查找,找到为止:

classA->classB->classC->classE->classF->classG->classH->classD->-classobject

 #经典类不继承object
class D:
def test(self):
print('from D')
# pass
class C(D):
def test(self):
print('from C')
# pass
class B(C):
def test(self):
print('from B')
# pass
class F(D):
def test(self):
print('from F')
# pass
class E(F):
def test(self):
print('from E')
# pass
class H(D):
def test(self):
print('from H')
# pass
class G(H):
def test(self):
print('from G')
# pass class A(B,E,G):
def test(self):
print('from A')
# pass obj=A()
obj.test()

经典类(python2中才有经典类的概念,python3中都是新式类)使用的是深度优先的方式,调用obj.test(),首先找boj对象的__dict__字典,然后找生成类A的__dict__字典,如果这两个都没有,会按照以下顺序进行查找,找到为止:

ClassA->ClassB->ClassC->ClassD->ClassE->ClassF->ClassG

mro方法

python的继承顺序,是按照一定的算法生成的mro表进行顺序查找继承的,只有在新式类中才有该方法:该方法有以下三个特点:

1.子类会先于父类被检查:

2.多个父类会根据它们在列表中的顺序被检查

3.如果对下一个类存在两个合法的选择,选择第一个父类

例如示例二有共同头部父类的模型,新式类mro输出表如下,按照表顺序进行继承:

 print(A.mro())
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.H'>, <class '__main__.D'>, <type 'object'>]

子类调用父类的方法(内置函数super)

low版调用方法,还是那个teacher还是那个people:

 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__函数
self.salary=salary
self.level=level
def foo(self):
print('from child') t=Teacher('bob',18,'male',3000,10)
print(t.name,t.age,t.sex,t.salary,t.level)
t.foo()

low版调用方法,在更改父类的名字之后,需要改动的地方除了子类继承的父类名字,还要改子类里面调用的父类名,比较麻烦

高端大气调用方式:只需要改动子类继承的父类名,即括号里的父类名字

 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):
#在python3中
super().__init__(name,age,sex) #调用父类的__init__的功能,实际上用的是绑定方法,用到了mro表查询继承顺序,只能调用一个父类的功能
#在python2中
# super(Teacher,self).__init__(name,age,sex) #super(Teacher,self)是一个死格式
self.salary=salary
self.level=level
def foo(self):
super().foo()
print('from child') t=Teacher('bob',18,'male',3000,10)
print(t.name,t.age,t.sex,t.salary,t.level)
t.foo()

但是这种方式也有一个缺点,就是当一个子类继承了多个父类的时候,如果多个父类都包含了相同的属性名,当要调用该功能的时候,只能调用第一个父类的功能,无法实现多个父类同时调用。多个父类同时调用还是要用low版方法。

封装

封装是一种隐藏的方式,包括数据封装和功能封装,即类里的数据属性和功能属性,隐藏数据和功能是为了限制直接调用,通过人为的添加调用接口进行数据和功能的调用。

封装不是单纯意义的隐藏:(史上最lowB的解释)

  1:封装数据的主要原因是:保护隐私(作为男人的你,脸上就写着:我喜欢男人,你害怕么?)

  2:封装方法的主要原因是:隔离复杂度,提供简单的访问接口(快门就是傻瓜相机为傻瓜们提供的接口,该方法将内部复杂的照相功能都隐藏起来了,拍照只需要通过快门这个接口就可以了,再比如你不必知道你自己的尿是怎么流出来的,你直接掏出自己的接口就能用尿这个功能)

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

封装的两个层面

基础的封装(什么都不用做):创建类和对象会创建各自的名称空间,通过类名或者对象的方式去访问类或对象里面的数据属性和功能属性。

还是这个people

 class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def foo(self):
print('from parent')
print(People.__dict__)
p=People('natasha',18,'female')
print(p.name)
p.foo()

通过p.name访问到了natasha,通过p.age访问到了18,这一类就是最基础的类和对象的封装,而p.name、p.foo()就是接口,访问数据属性和功能属性的接口。

二层封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

封装方式:在python中用双下划线的方式实现隐藏属性(设置成私有的)

 class Teacher:
__school='oldboy' #实际上转换成了_Teacher__school
def __init__(self,name,salary):
self.name=name
self.__salary=salary #实际上转换成了self._Teacher__salary
def __foo(self):
print('====foo====')
t=Teacher('egon',3000) # print(t.__school) #无法调用
print(Teacher.__dict__)
# t.foo() #无法调用
t._Teacher__foo()
# print(t.salary) #无法调用
# print(t.__salary) #无法调用
print(t.__dict__)
print(t._Teacher__salary)

python中的隐藏并不是真正意义上的隐藏,而是通过语法这一层面进行转换,虽然无法直接通过例如t.__salary或t.salary的方式调用,但是实际上在类的__dict__中可以查看到变形后的调用方式

类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式,但是这种变形操作只在定义阶段发生,后边手动添加的不会自动变形

 Teacher.__N=111111
print(Teacher.__dict__)
t.__x=1
print(t.__dict__)
输出结果:
{'__module__': '__main__', '_Teacher__school': 'oldboy', '__init__': <function Teacher.__init__ at 0x00000296CDD5B8C8>, '_Teacher__foo': <function Teacher.__foo at 0x00000296CDD5B950>, '__dict__': <attribute '__dict__' of 'Teacher' objects>, '__weakref__': <attribute '__weakref__' of 'Teacher' objects>, '__doc__': None, '__N': 111111}
{'name': 'egon', '_Teacher__salary': 3000, '__x': 1}

在类的外部,无法直接使用变形的属性,但是在类的内部可以直接使用

 class Teacher:
__school='oldboy' #_Teacher__school='oldboy'
def __init__(self,name,salary):
self.name=name
self.__salary=salary #self._Teacher__salary=salary def foo(self):
print('====>',self.__salary) #内部可以调用
# print('====>',self._Teacher__salary)
t=Teacher('egon',3000) # print(t.__salary) #外部无法调用
t.foo()

当子类和父类有相同的功能属性,两个类里变形过的功能可以分别调用:

不变形的功能只能调用到子类里的,无法调用父类的func功能

 class Foo:
def func(self):
print('from Foo')
class Bar(Foo):
def func(self):
print('from Bar')
b=Bar()
b.func()

变形后可以分别调用

 class Foo:
def __func(self): #_Foo__func
print('from Foo') class Bar(Foo):
def __func(self): #_Bar__func
print('from Bar')
b=Bar()
b._Foo__func()
b._Bar__func()

类里的功能属性和功能属性间调用:

A类和B类同时包含bar功能,A类通过foo功能调用自己的bar功能,通过B实例化b对象,当b对象调用foo的时候,由于B类没有foo功能,所以从A类中找foo功能,找到后调用,并在执行foo功能的过程中调用bar功能,按照mro表顺序查找,通过B类内找到bar功能并执行

 class A:
def foo(self):
print('from A.foo')
self.bar()
def bar(self):
print('from A.bar')
class B(A):
def bar(self):
print('from B.bar')
b=B()
b.foo()
输出结果
from A.foo
from B.bar

变形后调用:定义的过程中已经变形了,所以foo功能在找bar函数的时候实际上找的是变形后的_A__bar()功能

 class A:
def foo(self):
print('from A.foo')
self.__bar() #self._A__bar()
def __bar(self): #_A__bar()
print('from A.bar')
class B(A):
def __bar(self): #_B__bar b=B()
b.foo()

隐藏所有直接调用属性,通过接口的方式调用属性:又来了,还是那个people

 class People:
def __init__(self,name,age,sex,height,weight):
self.__name=name
self.__age=age
self.__sex=sex
self.__height=height
self.__weight=weight
#name、age、sex、height、weight都是经过变形后存储的,所以在调用的时候没办法直接调用,当然了要调用是可以的
def tell_name(self):
print(self.__name)
#通过手动创建接口的方式返回name的内容,屏蔽了直接调用
def set_name(self,val):
if not isinstance(val,str):
raise TypeError('名字必须是字符串类型')
self.__name=val
#通过手动创建修改接口修改name的属性值,屏蔽了直接调用
def tell_info(self):
print('''
---------%s info
name:%s
age:%s
sex:%s
height:%s
weight:%s
''' %(self.__name,
self.__name,
self.__age,
self.__sex,
self.__height,
self.__weight))
#通过手动创建接口,展示所有的信息

测试验证:

 bob=People('bob',18,'male','179cm','70kg')    #实例化对象
bob.tell_info() #通过接口查看bob的所有信息
bob.tell_name() #通过接口查看name属性
# bob.set_name(123)
bob.set_name('natasha') #通过接口修改name属性值
bob.tell_info()

property:封装的特性之一

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

手动创建的接口都是函数接口,函数接口在调用的时候都需要加()执行才能调用,如上边的例子bob.tell_name()通过接口查询name属性,基于用户角度来讲,比较显得美好简单的调用方式是bob.name,用户心理毛病多:我只是想看一下名字,为什么要我执行的这个东西?

示例:计算bmi健康指数

 class People:
def __init__(self,name,age,sex,height,weight):
self.__name=name
self.__age=age
self.__sex=sex
self.__height=height
self.__weight=weight @property #bmi=property(bmi),是一个内置函数,本质就是个装饰器
def bmi(self):
res=self.__weight / (self.__height ** 2)
return res

测试验证:

 bob=People('bob',18,'male',1.79,70)
print(bob.bmi)  #当调用bob.bmi时候,会返回res的值

使用这种方式,遵循了统一访问的原则,即用户感知不到我是执行了一个函数才获取的值。

但是仅仅这样,还是有问题,比如我想要删除一个属性,是无法删除的,比如del bmi,会提示AttributeError: can't delete attribute,想要通过bob.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 #支持obj.name='NAME'的方式执行
def name(self,val):
if not isinstance(val,str):
raise TypeError('must be str')
self.__name=val @name.deleter #支持del删除操作
def name(self):
if not self.permission:
raise PermissionError('不让删')
del self.__name

测试验证:

 natasha.name=123
print(natasha.name)
print(natasha.permission)
natasha.permission=True #不改成True,if认证不通过会删除失败
del natasha.name
#print(egon.name) #无法查询,已删除

Python开发基础-Day20继承实现原理、子类调用父类的方法、封装的更多相关文章

  1. python基础之继承实现原理、子类调用父类的方法、封装

    继承实现原理 python中的类可以同时继承多个父类,继承的顺序有两种:深度优先和广度优先. 一般来讲,经典类在多继承的情况下会按照深度优先的方式查找,新式类会按照广度优先的方式查找 示例解析: 没有 ...

  2. python子类调用父类的方法

    python子类调用父类的方法 python和其他面向对象语言类似,每个类可以拥有一个或者多个父类,它们从父类那里继承了属性和方法.如果一个方法在子类的实例中被调用,或者一个属性在子类的实例中被访问, ...

  3. python中子类调用父类的方法

    1子类调用父类构造方法 class Animal(object): def __init__(self): print("init Animal class~") def run( ...

  4. python学习-65 继承2-子类中调用父类的方法

    子类中调用父类的方法 1.子类继承了父类的方法,然后想进行修改,那么就需要在子类中调用父类的方法. 2.方法一:父类名 class School: Country = 'china' def __in ...

  5. Day7 子类调用父类的方法supper 绑定方法与非绑定方法

    supper:可以利用supper来重用父类的方法,可以不用指名道姓的调用了. class OldboyPeople: school = 'oldboy' def __init__(self,name ...

  6. Python开发基础-Day18继承派生、组合、接口和抽象类

    类的继承与派生 经典类和新式类 在python3中,所有类默认继承object,但凡是继承了object类的子类,以及该子类的子类,都称为新式类(在python3中所有的类都是新式类) 没有继承obj ...

  7. java继承-子类调用父类的方法中包含子类重写的方法

    # 看题目是不是很绕,这个我也不知道怎么才能更简单的表达了... # 先看代码: public class Common { public static void main(String[] args ...

  8. Python开发基础-Day19继承组合应用、对象序列化和反序列化,选课系统综合示例

    继承+组合应用示例 class Date: #定义时间类,包含姓名.年.月.日,用于返回生日 def __init__(self,name,year,mon,day): self.name = nam ...

  9. C++ 子类调用父类的方法,静态方法的调用

    #include <iostream> class  A { public: A(); ~ A(); virtualvoid say() { std::cout << &quo ...

随机推荐

  1. 【BZOJ4868】期末考试 [三分][贪心]

    期末考试 Time Limit: 20 Sec  Memory Limit: 512 MB[Submit][Status][Discuss] Description Input Output Samp ...

  2. 在非ARC工程中使用ARC库

    选中工程->TARGETS->相应的target然后选中右侧的“Build Phases”,向下就找到“Compile Sources”了.为对应的库文件添加:-fobjc-arc参数即可 ...

  3. python学习笔记(七)之列表

    列表:是一个加强版的数组,什么东西都可以往里面放. 创建列表 创建一个普通列表: >>> member = ['operating system', 'data structure' ...

  4. springcloud(一):大话Spring Cloud(山东数漫江湖)

    研究了一段时间spring boot了准备向spirng cloud进发,公司架构和项目也全面拥抱了Spring Cloud.在使用了一段时间后发现Spring Cloud从技术架构上降低了对大型系统 ...

  5. H题 hdu 2520 我是菜鸟,我怕谁

    题目大意:http://acm.hdu.edu.cn/showproblem.php?pid=2520 我是菜鸟,我怕谁 Time Limit: 2000/1000 MS (Java/Others)  ...

  6. 【Python学习笔记】Pandas库之DataFrame

    1 简介 DataFrame是Python中Pandas库中的一种数据结构,它类似excel,是一种二维表. 或许说它可能有点像matlab的矩阵,但是matlab的矩阵只能放数值型值(当然matla ...

  7. java===java习题---Josephu问题

    package testbotoo; /** * * @author */ public class Demo4 { public static void main(String[] args) { ...

  8. python3使用xlrd、xlwt、xlutils、openpyxl、xlsxwriter操作excel

    特色简介 xlrd主要用来读excel,针对.xls格式: xlwt主要用来写excel,针对.xls格式,超出excel 的单格内容长度上限32767,就会报错: xlutils结合xlrd可以达到 ...

  9. C后端设计开发 - 第7章-真气-遗失的网络IO

    正文 第7章-真气-遗失的网络IO 后记 如果有错误, 欢迎指正. 有好的补充, 和疑问欢迎交流, 一块提高. 在此谢谢大家了. ボクらの冒険 : http://music.163.com/#/m/s ...

  10. Python-生成器/你不知道的点

    1.什么是生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素, ...