面向对象(OOP:Object Oriented Programming)3 -- 基本概念

面向对象OOP:Object Oriented Programming

10 封装(Encapsulation)

  • 将数据和操作组织到类中
  • 将数据隐藏起来,给使用者提供操作。使用者通过操作就可以获取或修改数据,getter,setter
  • 通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,保护成员和私有成员

11 继承(Inheritance)

如前所述:人类Person和猫类Cat都继承自动物类Animal
个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。
在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多复用
子类可以定义自己的属性和方法。

继承: Class SubClassName(FatherClassName),这种形式就是从父类继承,括号中写上继承的类的列表。
继承可以让子类从父类获取特征(属性和方法)

父类: 又称基类、base类、超类
子类: 又称派生类

继承定义: Class子类名(基类1[,基类2,…]);如果没有基类列表,等同于继承自object类。python3中object类是所有对象的根基类

继承的特殊属性和方法:

属性含义说明
__base__类的基类
__bases__类的基类元组实例无此属性
__mro__显示方法查找顺序,返回基类的元组返回元组
mro()__mro____mro__就是mro()的返回值,但是mro()是一个列表
__subclasses__()所有子类列表列表

示例:

class Animal:
    HEIGHT = 0
    __COUNT = 0

    def __init__(self, age, weight, height):
        self.__age = age
        self.__weight = weight
        self.HEIGHT = height

    @classmethod
    def shout_count1(cls):
        print(cls.__COUNT)

    def __get_weight(self):
        print(self.__weight)

    def eat(self):
        print('{} eat'.format(self.__class__.__name__))


class Garfield(Animal):
    NAME = 'Garfield'

    def get_age(self):
        print(self.__age)


class Dog(Animal):
    def __init__(self, name):
        self.__name = name


if __name__ == '__main__':
    cat = Garfield(3, 5, 15)
    dog = Dog('dog')
    print(cat.HEIGHT)
    print(Animal.__dict__)
    print(Garfield.__dict__)
    print(cat.__dict__)
    print(dog.__dict__)
    print(cat.__class__.__mro__)
    print(Animal.__subclasses__())
    print(cat.__class__.__dict__)
    # print(cat.__COUNT)
    # print(cat.__get_weight)
    cat.shout_count1()
    cat.get_age()

执行结果:

15
{‘HEIGHT’: 0, ‘_Animal__COUNT’: 0, ‘init’: <function Animal.init at 0x000001CE620F9160>,
‘_Animal__get_weight’: <function Animal.__get_weight at 0x000001CE623399D0>, ‘eat’: <function Animal.eat at 0x000001CE623393A0>, …}
{‘module’: ‘main’, ‘NAME’: ‘Garfield’, ‘get_age’: <function Garfield.get_age at 0x000001CE62339AF0>, ‘doc’: None}
{‘_Animal__age’: 3, ‘_Animal__weight’: 5, ‘HEIGHT’: 15}
{‘_Dog__age’: ‘dog’}
(<class ‘main.Garfield’>, <class ‘main.Animal’>, <class ‘object’>)
[<class ‘main.Garfield’>, <class ‘main.Dog’>]

AttributeError: ‘Garfield’ object has no attribute ‘__COUNT’
AttributeError: ‘Garfield’ object has no attribute ‘__get_weight’

结论:

  • 从Garfield的实例cat的属性字典__dict__和Animal类的属性字典可以看出,继承符合私有属性的定义。类中以__双下划线开头的属性,属于私有成员,python解释器会自动将其重命名为_类名__属性名。实例属性self.__age是在Animal类中声明的,所以解释器将其重命名为_Animal__age;而Garfield类继承自Animal类,实例属性和类属性依然遵从私有属性的规则:私有self.__age在Animal类中定义的,所以是Animal类私有的,即使子孙类也不能拥有。
  • 类的属性只保存一份到__dict__中;子类的__dict__不会保存继承自父类的属性,需要的时候从父类找就可以了,例如cat.__class__.__mro__; 子类的dict`仅保存子类新增的属性。
  • 继承时,公有的,子类、实例都可以访问;而私有成员被隐藏,子类和实例不可以直接访问,例如:执行print(cat.__COUNT)时报AttributeError: 'Garfield' object has no attribute '__COUNT'错误,只有私有变量__COUNT、self.__age、self.__weight所在Animal类的方法才可以访问这个私有变量。
  • Dog类重写了初始化方法__init__,因此Dog类为继承父类的实例属性,只有重写的一个实例属性{'_Dog__name': 'dog'}
  • 属性的查找顺序:实例的__dict__ ->类的__dict__ -> 父类的__dict__,先找到立即返回,一直找到object类未找到抛异常。

11.1 方法重写override

super方法,很方便的访问祖先类;静态方法、类方法、一般属性都可以被重写覆盖。
示例:

class Animal:
    def __init__(self, name):
        self.__name = name

    @classmethod
    def shout_count(cls):
        print('Animal shout')

    @classmethod
    def shout(cls):
        print(cls, cls.__name__, 'shout')

    def eat(self):
        print('{} eat'.format(self.__class__.__name__))


class Garfield(Animal):
    def __init__(self, name, weight, height):
        # super方法,很方便的访问祖先类,
        # 等价于self.__class__.__base__.__init__(self, name)
        # 等价于Animal.__init__(self, name)
        super(Garfield, self).__init__(name)
        # Animal.__init__(self, name)
        self.name = name

    def eat(self):
        print('{} eat'.format(self.__class__.__name__))

    @staticmethod
    def shout_count():  # override
        print('Garfield shout count')


if __name__ == '__main__':
    cat = Garfield(3, 5, 15)
    cat.shout_count()
    cat.shout()
    cat.eat()
    print(cat.__dict__)
    print(Garfield.__dict__)
    print(Animal.__dict__)

执行结果:

Garfield shout count
<class ‘main.Garfield’> Garfield shout
Garfield eat
{‘_Animal__name’: 3, ‘name’: 3}
{‘eat’: <function Garfield.eat at 0x000002B2FFC49B80>, ‘shout_count’: <staticmethod object at 0x000002B2FFA31340>,…}
{‘shout_count’: <classmethod object at 0x000002B2FFA041F0>, ‘shout’: <classmethod object at 0x000002B2FFA040A0>, ‘eat’: <function Animal.eat at 0x000002B2FFC493A0>, …}

总结:

  • 子类Garfield实例cat调用继承自父类shout属性的调用,从执行结果<class '__main__.Garfield'> Garfield shout可以看出,虽然属性定义在父类, 但是cls由传入的参数决定,cls传入的是Garfield,所以打印的__name__是Garfield
  • 被重写的属性,在子类的__dict__就会保存这个属性,而继承的属性,只会保存在父类的__dict__中。

11.2 继承中的初始化

如下示例1,B类定义时声明继承自A类,在B类的__bases__中可以看到A类。但是这和是否调用A类的初始化方法__init__是两回事

  • 示例1,B调用了A的初始化方法__init__,所以拥有父类A的实例属性

示例1中,B的初始化方法__init__先调用了父类A类的初始化方法,且与B的实例self绑定,所以B拥有了
父类A的定义的实例属性self.aself.b;然后B又重新定义了self.b,覆盖继承自A类的实例属性
self.b,并新增了示例属性self.c

  • 如果B类的初始化方法__init__中没有显式调用父类A的初始化方法,解释器不会自动调用,B的实例不会拥有实例属性self.a

示例1:

class A:
    def __init__(self, a, b):
        self.a = a
        self.b = b

     
class B(A):
    def __init__(self, a, b):
        A.__init__(self, a+b, a-b)
        self.b = a
        self.c = b
    
    def printf(self):
        print(self.b)
        print(self.c)

        
class C(B):
    pass

if __name__ == '__main__':
    f = B(2, 3)
    print(f.__dict__)
    print(f.__class__.__bases__)

执行结果:

{‘a’: 5, ‘b’: 2, ‘c’: 3}
(<class ‘main.A’>,)

小结:良好的编码习惯,如果父类定义了初始化方法__init__,则子类应该调用一下父类A的__init__

什么时候子类会自动调用父类的初始化方法__init__

  1. 子类C未重新初始化方法,则子类C初始化会自动调用基类B的__init__方法。
    注意:其查找顺序遵从__mro__,第一次找到祖先类的__init__立即返回。
  2. 子类一旦重写了初始化方法__init__,就不会自动调用父类的__init__,必须显式调用才会好拥有父类的实例属性。

示例2:

class Animal:
    def __init__(self, age):
        self.__age = age

    def show(self):
        print(self.__age)


class Garfield(Animal):
    def __init__(self, age, weight):
        # 调用父类的__init__方法的顺序,决定了show方法的执行结果
        # 如果实例属性是私有成员,会发现,又有不一样的结果
        super(Garfield, self).__init__(age)
        self.age = age + 1
        # super(Garfield, self).__init__(age)

    def get_age(self, default=8):
        # 父类的初始化方法可以在子类的任何地方调用,调用位置不同可能引起不同结果,这一点一定要注意。
        super(Garfield, self).__init__(default)


if __name__ == '__main__':
    cat = Garfield(9, 5)
    cat.show()
    print(cat.__dict__)

小结:父类的初始化方法可以在子类的任何地方调用,调用位置不同可能引起不同结果,这一点一定要注意。

11.3、多继承

多继承很好的模拟了世界,因为事物很少是单一继承。

1. 多继承的弊端:

  • 多继承使问题复杂化;舍弃简单,必然引入了复杂性,带来了冲突。

比如:一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟该像谁多一点呢?

  • 多继承的实现导致编译器设计的复杂度增加。所有很多语言舍弃了类的多继承,C++支持多继承,Java舍弃了多继承。

  • 多继承可能会带来二义性。

例如猫和狗都继承自动物类,现在如果一个类多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢?

解决方案:实现多继承的语言,要解决二义性,深度优先或广度优先。

2. Python多继承实现

class ClassName('基类列表'):
    '类体'

多继承带来路径选择问题,究竟继承哪个父类的特征呢?
python使用MRO(method resolution order)解决基类搜索顺序问题。

历史原因,从python的最初版本到python3,MRO一共有三个搜索算法。2.3之后唯一支持C3算法。
C3算法,在类被创建出来的时候,就计算出一个MRO有序列表。
没必要深入理解C3算法,类设计出来后,需要查看子类继承的搜索顺序,直接执行SubClass.__mro__就可以了。

多继承的缺点:

  • 当类很多,继承复制的情况下,继承路径太多,很难说清什么样的继承路径。
  • python语法是允许多继承,但python代码是解释执行,只有执行到的时候才发现错误。
  • 团队协作开发,如果引入多继承,那代码将不可控。

不管编程语言是否支持多继承,都应当避免多继承。
项目开始时,一定要约定好,怎么命名变量名、类名、类的继承等等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个两个四个三

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值