首页 > 代码库 > python之面向对象(继承的实现原理及封装)

python之面向对象(继承的实现原理及封装)

一、继承的实现原理

继承的顺序

技术分享

class A(object):
    def test(self):
        print(from A)

class B(A):
    def test(self):
        print(from B)

class C(A):
    def test(self):
        print(from C)

class D(B):
    def test(self):
        print(from D)

class E(C):
    def test(self):
        print(from E)

class F(D,E):
    # def test(self):
    #     print(‘from F‘)
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

继承原理

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>]

子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法

方法一:父类名.父类方法()

技术分享
#_*_coding:utf-8_*_
__author__ = Linhaifeng

class Vehicle: #定义交通工具类
     Country=China
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print(开动啦...)

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)
        self.line=line

    def run(self):
        print(地铁%s号线欢迎您 %self.line)
        Vehicle.run(self)

line13=Subway(中国地铁,180m/s,1000人/箱,,13)
line13.run()
View Code

方法二:super()

技术分享
class Vehicle: #定义交通工具类
     Country=China
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print(开动啦...)

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print(地铁%s号线欢迎您 %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway(中国地铁,180m/s,1000人/箱,,13)
line13.run()
View Code

不用super引发的惨案

技术分享
#每个类中都继承了且重写了父类的方法
class A:
    def __init__(self):
        print(A的构造方法)
        
class B(A):
    def __init__(self):
        print(B的构造方法)
        A.__init__(self)
class C(A):
    def __init__(self):
        print(C的构造方法)
        A.__init__(self)
        
class D(B,C):
    def __init__(self):
        print(D的构造方法)
        B.__init__(self)
        C.__init__(self)
f1=D()
print(D.__mro__) 
‘‘‘
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
(<class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>)
‘‘‘
View Code

使用super()的结果

技术分享
class A:
    def __init__(self):
        print(A的构造方法)

class B(A):
    def __init__(self):
        print(B的构造方法)
        super().__init__()    #super(B,self).__init__()
class C(A):
    def __init__(self):
        print(C的构造方法)
        super().__init__()    #super(C,self).__init__()

class D(B,C):
    def __init__(self):
        print(D的构造方法)
        super().__init__()    #super(D,self).__init__()
        # C.__init__(self)
f1=D()
print(D.__mro__)
‘‘‘
D的构造方法
B的构造方法
C的构造方法
A的构造方法
(<class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>)
‘‘‘
View Code

当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

二、封装

1、要封装什么

封装数据和方法

2、为什么要封装

封装不是单纯意义的隐藏:

  1:封装数据的主要原因是:保护隐私

  2:封装方法的主要原因是:隔离复杂度

3、封装分为两个层面

封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问。

第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。

注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

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

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

类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class Teacher:
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print(姓名:%s,年龄:%s %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError(姓名必须是字符串类型)
        if not isinstance(age,int):
            raise TypeError(年龄必须是整型)
        self.__name=name
        self.__age=age

t=Teacher(egon,18)
t.tell_info()

t.set_info(egon,19)
t.tell_info()

这种自动变形的特点:

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。

 

这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形

三、propety函数的应用

# class Peopel:
#     def __init__(self,name,age,sex,weight,height,permission=False):
#         self.__name=name
#         self.__age=age
#         self.__sex=sex
#         self.__height=height
#         self.__weight=weight
#         self.permission=permission
#     @property
#     def info(self):
#         print("""
#         -----%s info-----
#         name:%s
#         age:%s
#         sex:%s
#         weight:%s
#         height:%s
#         """%(self.__name,self.__name,self.__age,self.__sex,self.__height,self.__weight))
#     @property
#     def bmi(self):
#         res=self.__weight / (self.__height ** 2)
#         return res
#     @property
#     def name(self):
#         return self.__name
#     @name.setter
#     def name(self,val):
#         if not isinstance(val,str):
#             raise TypeError(‘must be str‘)
#         self.__name=val
#     @name.deleter
#     def name(self):
#         if not self.permission:
#             raise PermissionError(‘不让删‘)
#         del self.__name
# egon=Peopel(‘egon‘,18,‘male‘,80,1.75)
# egon.info
# print(egon.bmi)
# print(egon.name)

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

 

python之面向对象(继承的实现原理及封装)