python-20 类的继承

类的继承

基本概念

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

不用继承的例子

class Animal:
    def shout(self):
        print('Animal shout')

a = Animal()
a.shout()   # Animal shout

class Cat:
    def shout(self):
        print('Cat shout')

c = Cat()
c.shout()   # Cat shout

使用继承的例子

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

    def shout(self):  # 一个通用的吃方法
        print('{} shout'.format(self.__class__.__name__))

    @property
    def name(self):
        return self._name

a = Animal('monster')
a.shout()  # Animal shout

class Cat(Animal):
    pass

cat = Cat('garfield')
cat.shout()   # Cat shout
print(cat.name)  # garfield

class Dog(Animal):
    pass

dog = Dog('ahuang')
dog.shout()   # Dog shout
print(dog.name)   # ahuang

上例可以看出,通过继承,猫类,狗类不用写代码,直接继承了父类的属性和方法。

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

父类
Animal就是Cat的父类,也称为基类、超类。

子类
Cat就是Animal的子类,也称为派生类。

定义

class 子类名(基类1[,基类2,...]):
    语句块

如果类定义时,没有基类列表,等同于继承自object。在Python3中,object类是所有对象的根基类。
class A: 等价于 class A(object):

Python支持多继承,继承也可以多级。
查看继承的特殊属性和私有方法有

特殊属性和方法 含义 示例
__base__ 类的基类
__bases__ 类的基类元组
__mro__ 显示方法查找顺序,基类的元组
mro()方法 同上 int.mro()
__subclasses__() 类的子类列表 int.subclasses()
class Animal:
    def __init__(self, name):
        self._name = name

    def shout(self):  # 一个通用的吃方法
        print('{} shout'.format(self.__class__.__name__))

    @property
    def name(self):
        return self._name

a = Animal('monster')
a.shout()  # Animal shout

class Cat(Animal):
    x = 'cat'
    def shout(self):
        print('miao')

class Garfield(Cat):
    pass

class PersiaCat(Cat):
    pass

class Dog(Animal):
    def run(self):
        print('Dog run')

tom = Cat('tom')
print(tom.name)
print(tom.shout())

dog = Dog('ahuang')
print(dog.name)
print(dog.shout())

gf = Garfield('gf')
gf.shout()
print('gf.x', gf.x)  # gf.x cat
print('gf', gf.__dict__)  # gf {'_name': 'gf'}

print('gf.mro={}'.format(gf.__class__.__mro__))  # gf.mro=(<class '__main__.Garfield'>, <class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>)
print('gf.bases={}'.format(gf.__class__.__bases__)) # gf.bases=(<class '__main__.Cat'>,)

print(Animal.__subclasses__())  # [<class '__main__.Cat'>, <class '__main__.Dog'>]
print(Cat.__subclasses__())  # [<class '__main__.Garfield'>, <class '__main__.PersiaCat'>]

继承中的访问控制

class Animal:
    def __init__(self, name):
        self._name = name
        self.__age = 10

    def shout(self):
        print('Animal shout')

    @property
    def name(self):
        return self._name

class Cat(Animal):
    x = 'cat'
    def shout(self):
        print('miao')

class Garfield(Cat):
    pass

class PersiaCat(Cat):
    pass

class Dog(Animal):
    def run(self):
        print('Dog run')

tom = Garfield('tom')
print(tom.name)  # tom
print(tom.shout())  # miao None 
print(tom.__dict__)  # {'_name': 'tom', '_Animal__age': 10}

print(Garfield.__dict__)  # {'__module__': '__main__', '__doc__': None}
print(Cat.__dict__)   # {'__module__': '__main__', 'x': 'cat', 'shout': <function Cat.shout at 0x000001BEF80364C8>, '__doc__': None}
print(Animal.__dict__)  # {'__module__': '__main__', '__init__': <function Animal.__init__ at 0x000001BEF8036318>, 'shout': <function Animal.shout at 0x000001BEF80363A8>, 'name': <property object at 0x000001BEF7DA6AE8>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}

我是一个子类,我继承了祖先的特征,祖先是抽象的,我是正真的个体,我才有继承数据的能力。面向对象的实现是有差别的,做为个性化的东西,每一个个体都是不同的东西,这个东西应该存多份。在python中设计成不同的字典。对于实例来讲,个性化数据,每一个实例都不同,所以叫实例属性。每一个实例创建出来后,都放在自己的dict中。

class Animal:
    def __init__(self, name):
        self._name = name
        self.__age = 10

    def shout(self):
        print('Animal shout')

    @property
    def name(self):
        return self._name

class Cat(Animal):
    name: object
    x = 'cat'

    def __init__(self, name):
        Animal.__init__(self, name)  
        self._name = "Cat" + name    
        self.__age = 20

    def shout(self):
        print('miao')
        Animal.__init__(self, 'abc')

class Garfield(Cat):
    pass

class PersiaCat(Cat):
    pass

class Dog(Animal):
    def run(self):
        print('Dog run')

tom = Cat('tom')
print(tom.__dict__)  # {'_name': 'Cattom', '_Animal__age': 10, '_Cat__age': 20}

Animal.__init__(self, name) 可以在任意位置
{'_name': 'Cattom', '_Animal__age': 10, '_Cat__age': 20} __age在哪个类里定义的私有属性,解释器就生成对应类的私有属性

class Animal:
    __COUNT = 0
    HEIGHT = 0

    def __init__(self, age, weight, height):
        self.__COUNT += 1
        self.age = age
        self.__weight = weight
        self.HEIGHT = height

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

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

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

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

class Cat(Animal):
    NAME = 'CAT'

#c = Cat()  # TypeError: __init__() missing 3 required positional arguments: 'age', 'weight', and 'height'
c = Cat(3, 5, 15)
c.eat()  # Cat eat
print(c.HEIGHT)  # 15
# print(c.__COUNT)  # AttributeError: 'Cat' object has no attribute '__COUNT'
# c.__showweight()  # AttributeError: 'Cat' object has no attribute '__showweight'  私有的不可访问
print(c.NAME)  # CAT

print("{}".format(Animal.__dict__))  # {'__module__': '__main__', '_Animal__COUNT': 0, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x000002EEE84B71F8>, 'eat': <function Animal.eat at 0x000002EEE84B7288>, '_Animal__getweight': <function Animal.__getweight at 0x000002EEE84B7318>, 'showcount1': <classmethod object at 0x000002EEE84AED48>, '_Animal__showcount2': <classmethod object at 0x000002EEE84B6148>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
print("{}".format(Cat.__dict__)) # {'__module__': '__main__', 'NAME': 'CAT', '__doc__': None}
print(c.__dict__) # {'_Animal__COUNT': 1, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}
print(c.__class__.mro()) # [<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

从父类继承,自己没有的,就可以到父类中找
私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类的__dict__中,知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。

总结
继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可以直接访问,当私有变量所在的类内的方法中可以访问这个私有变量。
Python通过自己一套实现,实现和其他语言一样的面向对象的继承机制。

属性查找顺序
实例的 __dict__ -> 类 __dict__ 有继承 -> 父类 __dict__
如果搜索这些地方后没有找到就会跑异常,先找到就立即返回了

方法的重写、覆盖override

class Animal:
    def __init__(self, name):
        self._name = name
        self.__age = 10

    def shout(self):
        print('Animal shout')

    @property
    def name(self):
        return self._name

class Cat(Animal):
    name: object
    x = 'cat'

    def __init__(self, name):
        #Animal.__init__(self, name)
        super().__init__(name)   # 推荐
        self._name = "Cat" + name
        self.__age = 20

    def shout(self):  # override
        super().shout()
        print('Cat shout')

class Garfield(Cat):
    def shout(self):  # override
        super().shout()
        print('Garfield shout')

class PersiaCat(Cat):
    pass

class Dog(Animal):
    def run(self):
        print('Dog run')

tom = Garfield('tom')
print(tom.shout())
###############
Animal shout
Cat shout
Garfield shout
None
class Animal:
    def shout(self):
        print('Animal shout')

class Cat(Animal):
    #覆盖了父类方法
    def shout(self):
        print('miao')

    # 覆盖了自身的方法,显示调用了父类的方法
    def shout(self):
        print(super())
        print(super(Cat, self))
        super().shout()
        super(Cat, self).shout()  # 等价于 super().shout()
        self.__class__.__base__.shout(self)  # 不推荐
		
a = Animal()
a.shout()   
c = Cat()
c.shout() 
#######################
Animal shout
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
Animal shout
Animal shout
Animal shout

super()可以访问到父类的属性。(执行向继承链查找到的第一个方法)

对于类方法和静态方法

class Animal:
    def __init__(self, name):
        self._name = name
        self.__age = 10

    def shout(self):
        print('Animal shout')

    @property
    def name(self):
        return self._name
		
    @classmethod
    def clsmtd(cls):
        print(cls, cls.__name__)

class Cat(Animal):
    name: object
    x = 'cat'

    def __init__(self, name):
        #Animal.__init__(self, name)
        super().__init__(name)   # 推荐
        self._name = "Cat" + name
        self.__age = 20

    def shout(self):  # override
        super().shout()
        print('Cat shout')

    @classmethod
    def clsmtd(cls):    # 
        print(cls, cls.__name__)

class Garfield(Cat):
    pass

class PersiaCat(Cat):
    pass

class Dog(Animal):
    def run(self):
        print('Dog run')

tom = Garfield('tom')
print(tom.clsmtd())  # <class '__main__.Garfield'> Garfield
print(Cat.__dict__)  # {'__module__': '__main__', '__annotations__': {'name': <class 'object'>}, 'x': 'cat', '__init__': <function Cat.__init__ at 0x000001DBA6DD8798>, 'shout': <function Cat.shout at 0x000001DBA6DD8828>, 'clsmtd': <classmethod object at 0x000001DBA6DD3988>, '__doc__': None}
print(Animal.__dict__)  # {'__module__': '__main__', '__init__': <function Animal.__init__ at 0x000001DBA6DD8558>, 'shout': <function Animal.shout at 0x000001DBA6DD85E8>, 'name': <property object at 0x000001DBA6B46AE8>, 'clsmtd': <classmethod object at 0x000001DBA6DD3808>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}

print(tom.clsmtd()) 输出实例自己的类名 Garfield
这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。

继承中的初始化


class A:
    def __init__(self, a, d=10):
        self.a = a
        self__d = d

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

    def printv(self):
        print(self.b)
        print(self.a)   

f = B(200,300)
print(f.__dict__)  # {'a': 500, 'b': 200, 'c': 300}
print(f.__class__.__bases__)  # (<class '__main__.A'>,)
f.printv()   # 200
            #500

作为好的习惯,如果父类中定义了__init__方法,你就该在子类的__init__中调用它(父类的__init__)
子类的__init__方法中,应该显示调用父类的__init__方法

class Animal:
    def __init__(self, age):
        print('Animal init')
        self.age = age

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

class Cat(Animal):
    def __init__(self, age, weight):
        # 调用父类的__init__方法的顺序决定着show方法的结果
        super().__init__(age)
        print('Cat init')
        self.age = age + 1
        self.weight = weight
        # super().__init__(age)
		
c = Cat(10, 5)  # Animal init
                # Cat init
c.show()        # 11
print(c.__dict__)  # {'age': 11, 'weight': 5}

注意,调用父类的__init__方法,出现在不同的位置,可能导致出现不同的结果

那么,直接将上例中所有的实例属性改成私有变量呢?

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

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

class Cat(Animal):
    def __init__(self, age, weight):
        # 调用父类的__init__方法的顺序决定着show方法的结果
        super().__init__(age)
        print('Cat init')
        self.__age = age + 1
        self.__weight = weight

c = Cat(10, 5)  # Animal init
                # Cat init
c.show()        # 10
print(c.__dict__)  # {'_Animal__age': 10, '_Cat__age': 11, '_Cat__weight': 5}

上例中打印10,原因看__dict__就知道了。因为父类Animal的show方法中__age会被解释为_Animal__age,因此显示的是10,而不是11。(show方法是Animal的,使用Animal的私有变量)
这样的设计不好,Cat的实例 c 应该显示自己的属性值更好。

解决的方法: 一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的,即使是父类或者派生类的方法。

多继承

Python不同版本的类

Python2.2 之前类是没有共同的祖先的,之后,引入object类,它是所有类的共同祖先类object
Python2 中为了兼容,分为古典类(旧式类)和新式类
Python3 中全部都是新式类
新式类都是继承自object的,新式类可以使用super

多继承

OCP原则:多继承、少修改
继承的用途:增强基类、实现多态

多态
在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态
一个类继承自多个类就是多继承,它将具有多个类的特征

多继承弊端

多继承很好的模拟了世界,因为事物很少的是单一继承,但是舍弃简单,必然引入复杂性,带来冲突
如同一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟像谁多一个点呢?

多继承的实现会导致编译器设计的复杂度增加,所以现在很多语言也舍弃了类的多继承
多继承可能带来二义性,例如,猫狗都继承自动物类,现在如果一个类多继承了猫和狗类,猫和狗类都有shout方法,子类究竟继承谁的shout呢?

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

多继承实现

class ClassName(基类列表):
    类体


多继承带来路径选择问题,究竟继承哪个父类的特征呢
Python使用 MRO (method resolution order) 解决基类搜索顺序问题
历史原因,MRO有三个搜索算法:
经典算法:按照定义从左到右,深度优先策略。2.2之前右图的MRO是 MyClass,D,B,A,C,A (找到后即停止搜索)
新式类算法:经典算法的升级,重复的保留最后一个。2.2右图的MRO是MyClass,D,B,(A),C,A,object (第一个A舍弃)
C3算法:在类被创建出来的时候,就计算出一个有序列表。2.3之后,Python3唯一支持的算法
,右图中的MRO是MyClass,D,B,C,A,object的列表,C3算法解决多继承的二义性

多继承的缺点

当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径
Pyhton语言是允许多继承,但Python代码是解释执行,只有执行到的时候,才发现错误
团队协作开发,如果引入多继承,那代码将不可控
不管编程语言是否支持多继承,都应避免多继承。
Python的面向对象,我们看到的太灵活了,太开发了,所以要团队守规矩

Mixin(混合)

类有下面的继承关系
enter description here
文档Document类是其他所有文档类的抽象基类;
Word、Pdf类是Document的子类。

需求:为Document子类提供打印能力
思路:
1、在Document中提供print方法
基类提供的方法不应该具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。print算是一种能力 ——打印功能,不是所有的Document的子类都需要的,所以从这个角度出发,有点问题。
2、需要打印的子类上增加
如果在子类上直接增加,违反了OCP的原则,所以因该继承后增加。因此有下图
enter description here

class Docment:
    def __init__(self, content):
        self.content = content

    def print(self):
        print(self.content)

class Word(Docment):  # 第三方库,不允许修改
    pass

class PrintableWord(Word):
    def print(self):
        print('Word print {}'.format(self.content))

class Pdf(Docment):
    pass

print(PrintableWord.mro())  #  [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Docment'>, <class 'object'>]
word = PrintableWord('test\nabc')
print(word.__dict__)  # {'content': 'test\nabc'}
word.print()   # Word print test
               # abc
class Printable:
    def _print(self):
        print(self.content)

class Docment:  # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content

class Word(Docment): pass # 第三方库,不允许修改
class Pdf(Docment):  pass  # 第三方库,不允许修改

class PrintableWord(Printable, Word): pass
print(PrintableWord.__dict__)  # {'__module__': '__main__', '__doc__': None}
print(PrintableWord.mro())  # [<class '__main__.PrintableWord'>, <class '__main__.Printable'>, <class '__main__.Word'>, <class '__main__.Docment'>, <class 'object'>]

pw = PrintableWord('test string')
pw._print()  # test string

看似不错,如果需要还要提供其他能力,如何继承?应用于网络,文档应该具备序列化的能力,类上就应该实现序列化。可序列化还可能分为使用pickle、json、messagepack等
这个时候发现,类可能太多了,继承的方式不是很好了
功能太多,A类需要某几样功能,B类需要另几样功能,很繁琐

3、装饰器
用装饰器增强一个类,把功能附加上去,那个类需要,就装饰它

def printable(cls):
    cls.print = lambda self:print(self.content)
    return cls

@printable
class Printable:  # Printable = printable(Printable)
    def __init__(self, content):
        self.content = content
def printable(cls):
    def _print(self):
        print(self.content, '装饰器')
    cls.print = _print
    return cls

class Docment:
    def __init__(self, content):
        self.content = content

class Word(Docment):  pass
class Pdf(Docment):pass

@printable  # 先继承,后装饰
class PrintableWord(Word): pass
print(PrintableWord.__dict__)  # {'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x000002C9223D6168>}
print(PrintableWord.mro())  # [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Docment'>, <class 'object'>]

pw = PrintableWord('test string')
pw.print()  # test string  # test string 装饰器

@printable
class PrintablePdf(Word): pass

优点:简单方便,在需要的地方动态增加,直接是使用装饰器

4、Mixin

class PrintableMixin:
    def print(self):
        print('~~~~~~~~~~~~~~~~')
        print('{}'.format(self.content ))
        print('~~~~~~~~~~~~~~~~')

class Document:
    def __init__(self, content):
        self.content = content

    def print(self):
        print(self.content)

def printable(cls):
    def _print(self):
        print('~~~~~~~~~~~~~~~~')
        print('{}'.format(self.content ))
        print('~~~~~~~~~~~~~~~~')

    cls.print = _print()
    return cls

class Word(Document):
    pass

@printable
class PrintableWord(Word):
    pass

class Pdf(Document):
    pass

class PrintablePdf(PrintableMixin, Pdf):
    pass

print(PrintablePdf.mro())   # [<class '__main__.PrintablePdf'>, <class '__main__.PrintableMixin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]
word = PrintablePdf('test\nabc')
print(word.__dict__)  # {'content': 'test\nabc'}

word.print()   # ~~~~~~~~~~~~~~~~
               # test
               # abc
               # ~~~~~~~~~~~~~~~~

Mixin就是其它类混合进来,同时带来了类的属性和方法。
这里看来Mixin类和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。

class SuperPrintableMixin(PrintableMixin):
    def print(self):
        print('~' * 10)
        super().print()
        print('~' * 10)

class SuperPrintablePdf(SuperPrintableMixin, Pdf):
    pass

Mixin类

Mixin本质上就是多继承实现的
Mixin体现的是一种组合的设计模式

在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,这就需要很多的类组合在一起。
从设计模式的角度来说,多组合,少继承。

Mixin类的使用原则

  • Mixin类中不应该显式的出现__init__初始方法
  • Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
  • Mixin类的祖先类也应是Mixin类

Mixin类和装饰器
这两种方式都可以使用,看个人爱好
如果还需要继承就得用Mixin类的方式

练习

1、Shape基类,要求所有子类都必须提供面积的计算,子类有三角形、矩形、圆
2、上题圆类的数据可序列化

enter description here
用面向对象实现LinkedList链表
单向列表实现append、iternodes
双向列表实现append、pop、insert、remove、internodes

posted @ 2022-04-28 18:48  何时&明月  阅读(66)  评论(0)    收藏  举报