跳转至

第5章 面向对象编程基础

5.1 面向对象的概念与特征

5.1.1 面向对象的概念

  • 面向对象
  • 对象是程序的基本单元,程序是各种即有独立性又相互调用的对象构成

  • 编程范式

  • 过程式
    • 将程序看作一系列指令和函数的集合,根据一定的流程控制顺序来执行程序指令
    • 与面向对象相容
  • 函数式

    • 将待解决问题解构成一系列函数
    • 与面向对象不相容
  • 问题解构的角度来看

  • 面向对象编程是对现实世界的模拟,对象就是现实世界中的个体
  • 每个对象都具有自已的属性,还能够作出各种行为,对象之间还能相互作用
  • 例如,一个城市的交通系统中有很多的行人、车辆、信号灯等构成
  • 从程序设计的角度来看
  • 对象其实是一些“变量”和一些“函数”的组合体,是为了实现特定的程序功能不可或缺的组成部分
  • “变量”称为__属性__
  • “函数”称为__方法__

  • 面向对象编程的优点

  • 模块化程度高
    • 能够从更加抽象的层次去解构问题、分解任务
  • 便于代码重用
    • 够方便地利用一种对象的功能实现另一种类型的对象
    • 继承性
  • 封装性良好
    • 属性被封装到对象之中,对象在被调用时其内部实现过程对于调用者来说是透明的
    • 封装性
  • 灵活性高

    • 不同的类形的对象是能够相互替代的,对象能够以不同的身份参与到不同的功能调用之中
    • 多态性
  • 面向对象编程的现状

  • Simula:第一种具有面向对象特征的编程语言是
  • Smalltalk:一种完全面向对象的编程语言
  • 面向对象的思想影响了一大批编程语言

5.1.2 类与对象

  • 抽象
  • 从一类事物中归纳、提取出它们共同的、本质的特征,忽略与关注目标无关的、非本质特征,最终形成一个能够用于描述这类事物的概念
  • 抽象出的概念称为类
  • 具有普遍性、抽象性
  • 对象
  • 对象是类的实例
  • 个性化、具体的

定义类:

1
2
3
4
类 汽车:
    属性 车长;
    属性 车宽;
    方法 行驶(速度);
实例化并调用:
1
2
3
大黄峰是一辆汽车
大黄峰的宽1.9米,长4.2米
大黄峰.行驶(速度200)

5.1.3 封装性

  • 封装性的含义
  • 完成一类对象功能所需的数据及对数据的操作方法被打包为一个整体
  • 对外隐藏不必要的细节
  • 类成员的访问具有一定的权力限制

5.1.4 继承性

  • 继承(Inheritance)
  • 是一种创建新类的方法,能够有效地实现代码重用
  • 基类(父类)与派生类(子类)

1
2
3
4
5
6
7
类 客车 继承自 汽车:
    属性 最大载客量;
    方法 上下客(客人数量);

类 货车 继承自 汽车:
    属性 最大载重量;
    方法 装卸货物(货物数量);

5.1.5 多态性

  • 特征
  • 派生类对象由于具有父类对象的所有功能,因而可以作为父类对象使用
    • 例如,汽车类的对象能够行驶在机动车道上,客车类是汽车类的子类,其对象也能够行驶在机动车道上
  • 在被调用时每个对象可能以不同的方式做出响应

    • 例如,跑车的行驶与普通汽车有着显著的差异,因此跑车类需要重新定义行驶方法
    • 相同的指令,汽车对象与跑车对象会调用各自的方法做出不同的响应
  • 注意

  • 子类对象不会因为被当作父类对象使用就变成了父类对象
    • 客车虽然可以被当作汽车来使用,但是它本质上是客车,不会变成普通汽车
  • 子类对象被作为父类对象使用时,只能调用父类对象拥有的属性
    • 客车对象被作为汽车使用时,我们无法确定它究竟是什么,它可能是货车也可能是跑车,因而不能访问最大载客量变量或调用上下客方法
  • 子类重写了父类方法时,即使子类对象被当作父类对象使用,调用的也是子类中的方法
    • 一个跑车对象不论被看作汽车还是跑车,当行驶时被调用的只能是跑车中的行驶方法

5.2 类的定义与实例化

5.2.1 类的定义

1
2
3
class 类名:
    '''文档字符串'''
    类体
- 类名 - 合法的标识符 - 类体 - 文档字符串 - 属性定义 - 方法定义 - 任何合法的Python代码片段

1
2
3
4
5
class Auto:
    width = 0
    length = 0
    def run(self, speed):
        print(f"以速度{speed}行驶!")
  • 定义属性和方法
  • 属性:与普通变量定义一致
  • 方法

1
2
3
def 方法名(self, 参数1, 参数2, ...):
    '''文档字符串'''
    方法体
- self - 对象自身 - 通过self来访问对象中包含的其他属性或方法

1
2
3
4
5
class Auto:
    width = 0
    length = 0
    def run(self, speed):
        print(f"本车长{self.width},宽{self.length},以速度{speed}行驶!")
  • self参数的名称可以任意

5.2.2 类的实例化

  • 实例化
1
2
3
4
auto = Auto()  # 实例化
auto.width = 1.5
auto.length = 3.3
auto.run(80)
1
本车长1.5,宽3.3,以速度80行驶!
  • 实例化的过程
  • 由两个特殊方法__new____init__实现
    • 如果用户没有定义__new____init__默认会调用父类的__new____init__方法
1
2
3
4
5
6
7
8
class Test:
    def __init__(self):
        print(f'{id(self)} in __init__')

    def __new__(cls):           # 类方法
        o = object.__new__(cls)
        print(f'{id(o)} in __new__')
        return o
1
obj = Test()
1
2
140232540383456 in __new__
140232540383456 in __init__
  • 实例化过程中参数的传递
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Test:
    def __init__(self, *args, **kwargs):
        print(f'{args} in __init__')
        print(f'{kwargs} in __init__')

    def __new__(cls, *args, **kwargs):
        o = object.__new__(cls)
        print(f'{args} in __new__')
        print(f'{kwargs} in __new__')
        return o
1
obj = Test(1, x=2)
1
2
3
4
(1,) in __new__
{'x': 2} in __new__
(1,) in __init__
{'x': 2} in __init__
1
2
3
4
5
6
class Auto:
    def __init__(self, width, length):
        self.width = width
        self.length = length
    def run(self, speed):
        print(f"本车长{self.width},宽{self.length},以速度{speed}行驶!")
1
2
auto = Auto(1.5, 3.3)
auto.run(80)
1
本车长1.5,宽3.3,以速度80行驶!

5.2.3 类成员的隐藏

  • 所有名称以__开头的属性为隐藏成员
1
2
3
4
5
6
7
class TestHidden:
    def __init__(self):
        self.__hidden = 1
    def out(self):
        print(f'the hidden value is {self.__hidden}')
    def __hidden_value(self):
        return self.__hidden
1
2
th = TestHidden()
th.out()
1
the hidden value is 1
1
th.__hidden
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-12-1a5cd90a4d1d> in <module>
----> 1 th.__hidden


AttributeError: 'TestHidden' object has no attribute '__hidden'
1
th.__hidden_value()
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-13-79203ffd8557> in <module>
----> 1 th.__hidden_value()


AttributeError: 'TestHidden' object has no attribute '__hidden_value'
  • Python中没有真正的“私有”
1
th._TestHidden__hidden
1
1
1
th._TestHidden__hidden_value()
1
1
  • 一种约定
  • _开头的为隐藏成员

5.2.4 类命名空间 *

  • Python中的命名空间
  • 内建命名空间:Python解释器启动时创建的命名空间;
  • 全局命名空间:加载模块时创建的命名空间;
  • 闭包命名空间:定义闭包时创建的命名空间;
  • 局部命名空间:定义函数或类时创建的命名空间。
1
type(globals())
1
dict
1
globals()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{'__name__': '__main__',
 '__doc__': 'Automatically created module for IPython interactive environment',
 '__package__': None,
 '__loader__': None,
 '__spec__': None,
 '__builtin__': <module 'builtins' (built-in)>,
 '__builtins__': <module 'builtins' (built-in)>,
 '_ih': ['', 'globals()'],
 '_oh': {},
 '_dh': ['/Users/liuchen/Documents/GitHub/python_book/notebook_slides'],
 'In': ['', 'globals()'],
 'Out': {},
 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7fc4d818fd00>>,
 'exit': <IPython.core.autocall.ZMQExitAutocall at 0x7fc4d81fe580>,
 'quit': <IPython.core.autocall.ZMQExitAutocall at 0x7fc4d81fe580>,
 '_': '',
 '__': '',
 '___': '',
 'json': <module 'json' from '/Users/liuchen/anaconda3/envs/lesson/lib/python3.8/json/__init__.py'>,
 'autopep8': <module 'autopep8' from '/Users/liuchen/anaconda3/envs/lesson/lib/python3.8/site-packages/autopep8.py'>,
 '_i': '',
 '_ii': '',
 '_iii': '',
 '_i1': 'globals()'}
  • 类命名空间
  • 当定义一个类之后,就产生了一个命名空间
1
2
3
4
5
6
7
8
class TestNamespace:
    value_test = 1
    def method_test(self):
        pass
    print(locals())

def f(self, x):
    pass
1
{'__module__': '__main__', '__qualname__': 'TestNamespace', 'value_test': 1, 'method_test': <function TestNamespace.method_test at 0x7f8a6ecb6670>}

5.3 进一步了解属性

5.3.1 类属性与实例属性

  • 定义在类命名空间中的属性称为类属性或静态属性
  • 类的所有对象共享相同的类属性
  • 类属性对于类内的方法来说类似于全局变量。当在方法中试图修改类属性的取值时,相当于重新定义了局部变量,而局部变量会覆盖掉类命名空间中的同名变量。
  • 定义在self对象中的属性称为实例属性
  • 每个对象自有
1
2
class Test1:
    x = [0]
1
2
3
4
t11 = Test1()
t12 = Test1()
t11.x[0] = 1
print(t11.x, t12.x)
1
[1] [1]
1
2
3
class Test2:
    def __init__(self):
        self.x = [0]
1
2
3
4
t21 = Test2()
t22 = Test2()
t21.x[0] = 1
print(t21.x, t22.x)
1
[1] [0]

5.3.2 property装饰器

  • 直接访问类属性
1
2
3
class Moto1:
    def __init__(self):
        self.oil = 0
1
2
m1 = Moto1()
m1.oil
1
0
  • 间接访问类属性
1
2
3
4
5
6
7
8
9
class Moto2:
    def __init__(self):
        self.__oil = 0

    def get_oil(self):
        return self.__oil

    def set_oil(self, oil):
        self.__oil = oil    
1
2
m2 = Moto2()
m2.get_oil()
1
0
  • property装饰器能够将方法转换为属性的形式来使用
1
2
3
4
5
6
7
class Moto:
    def __init__(self):
        self.__oil = 0

    @property
    def oil(self):
        return self.__oil
1
2
moto = Moto()
moto.oil
1
0
  • 完整的property
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Moto:
    def __init__(self):
        self.__oil = 0

    @property
    def oil(self):
        return self.__oil

    @oil.setter
    def oil(self, oil):
        self.__oil = oil

    @oil.deleter
    def oil(self):
        del self.__oil
1
2
moto = Moto()
moto.oil
1
0
1
2
moto.oil = 100
moto.oil
1
100
1
2
del moto.oil
moto.oil
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-33-88a69fe2191c> in <module>
      1 del moto.oil
----> 2 moto.oil


<ipython-input-30-4d7c99681068> in oil(self)
      5     @property
      6     def oil(self):
----> 7         return self.__oil
      8 
      9     @oil.setter


AttributeError: 'Moto' object has no attribute '_Moto__oil'

5.4 进一步了解方法

5.4.1 实例方法、类方法与静态方法

实例方法

  • 类中定义的普通方法第一个形参是self,表示对象自身。可以利用self来访问对象 的其他属性或方法,也可以在其他方法中使用self来访问当前方法。这类方法称为实例方法。

类方法

  • classmethod装饰器实现
  • 第一个参数名常为cls
  • 可以通过类直接调用,也可以通过对象调用
1
2
3
4
5
6
class TestClassMethod:
    class_value = 0

    @classmethod
    def class_method(cls):
        print(cls.class_value)
1
TestClassMethod.class_method()
1
0
1
2
tcm = TestClassMethod()
tcm.class_method()
1
0

静态方法

  • staticmethod装饰器实现
  • 无特殊参数
  • 可以通过类直接调用,也可以通过对象调用
1
2
3
4
class TestStaticMethod:
    @staticmethod
    def static_method():
        print('这里是静态方法!')
1
TestStaticMethod.static_method()
1
这里是静态方法!
1
2
tsm = TestStaticMethod()
tsm.static_method()
1
这里是静态方法!

5.4.2 方法重载*

  • singledispatchmethod装饰器实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from functools import singledispatchmethod

class Averager:
    @singledispatchmethod
    def read(self, data):
        n = len(data)
        self.value = sum(data)/n

    @read.register(dict)
    def _(self, data):
        data = data.values()
        n = len(data)
        self.value = sum(data)/n

    @read.register(str)
    def _(self, data):
        data = [float(i) for i in data.split(',')]
        n = len(data)
        self.value = sum(data)/n

5.5 类的继承

5.5.1 派生类的定义

  • 语法形式
1
2
class 派生类(基类名):
    类体
  • 默认的基类为object
  • 所有的类都是object类的派生类
  • object类中定义了很多特殊属性和特殊方法,子类重写这些方法能够对类进行定制以实现特殊的功能
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Moto:
    def __init__(self, width, length):
        self.width = width
        self.length = length

    def run(self, speed):
        print(f"本车为 Moto,长{self.width},宽{self.length},以速度{speed}行驶!")

class Wagon(Moto):
    pass

class Coach(Moto):
    pass

class Car(Coach):
    pass

class Bus(Coach):
    pass

5.5.2 方法的重写

  • 在派生类中重新定义了基类中的属性或方法时,基类中的同名属性或方法会被覆盖掉,称为属性或方法的重写
  • 基类的隐藏成员在派生类中也是不可见的
  • 基类中改变隐藏方法名称的原因

重写普通方法

1
2
3
class Wagon(Moto):
    def run(self, speed):
        print(f"本车为 Wagon,长{self.width},宽{self.length},以速度{speed}行驶!")
1
2
wagon = Wagon(2.2, 10)
wagon.run(50)
1
本车为 Wagon,长2.2,宽10,以速度50行驶!

重写__init__

  • 需在派生类的__init__方法中调用基类的__init__方法来初始化
1
2
3
4
5
6
7
8
9
class Wagon(Moto):
    def __init__(self, width, length, load):
        self.carrying_load = load            # 载重量
        Moto.__init__(self, width, length)

class Coach(Moto):
    def __init__(self, width, length, capacity):
        self.passenger_capacity = capacity   # 载客量
        Moto.__init__(self, width, length)

重写property*

  • property修饰的方法以及相应的setter方法和deleter方法是一个整体
  • 如果派生类中要重写基类中定义的property,不能仅重写被property修饰的方法,还需要将setter方法和deleter方法也重,才能保证派生类中property的完整性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Moto:
    def __init__(self):
        self.__oil = 0

    @property
    def oil(self):
        return self.__oil

    @oil.setter
    def oil(self, oil):
        self.__oil = oil

    @oil.deleter
    def oil(self):
        del self.__oil

class Wagon(Moto):
    @property
    def oil(self):
        return super().oil    # 调用基类对象中的oil
1
2
wagon = Wagon()
wagon.oil
1
0
1
wagon.oil = 1
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-47-9840aa297a61> in <module>
----> 1 wagon.oil = 1


AttributeError: can't set attribute
  • 重写property中的方法其中之一
1
2
3
4
5
6
7
8
class Wagon(Moto):
    @Moto.oil.getter
    def oil(self):
        return super.oil

    @Moto.oil.setter
    def oil(self, oil):
        super(Wagon, Wagon).oil.__set__(self, oil)
1
2
3
wagon = Wagon()
wagon.oil = 100
wagon.oil
1
100

5.5.3 多重继承*

  • 一个派生类继承自多个基类
  • 多重继承中会出现命名冲突问题,增加了程序的复杂性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Moto:
    def __init__(self, width, length): 
        self.width = width
        self.length = length

    def run(self, speed):
        print(f"本车为 Moto,长{self.width},宽{self.length},以速度{speed}行驶!")

class Wagon(Moto):
    def __init__(self, width, length, load):
        self.carrying_load = load            # 载重量
        Moto.__init__(self, width, length)

class Coach(Moto):
    def __init__(self, width, length, capacity):
        self.passenger_capacity = capacity   # 载客量
        Moto.__init__(self, width, length)

class Pickup(Wagon, Coach):
    def __init__(self, width, length, load, capacity):
        Wagon.__init__(self, width, length, load)
        Coach.__init__(self, width, length, capacity)

  • 方法解析顺序(Method Resolution Order,MRO)
  • 解释器依据该顺序进行搜索,执行找到的第一个方法
1
Pickup.__mro__
1
(__main__.Pickup, __main__.Wagon, __main__.Coach, __main__.Moto, object)

5.5.4 对象、类的关系

关系检查

  • isinstance
  • 检查一个对象是否是一个类的实例
1
2
pickup = Pickup(1.8, 3.9, 6, 2)
isinstance(pickup, Pickup)
1
True
1
isinstance(pickup, Wagon), isinstance(pickup, Coach), isinstance(pickup, Moto)
1
(True, True, True)
  • issubclass
  • 判断一个类是否另一个类的派生类
1
issubclass(Pickup, Wagon)
1
True
1
issubclass(Pickup, Coach)
1
True
1
issubclass(Pickup, Moto)
1
True

鸭子类型和协议

  • 动态语言特有的一种特征,其含义是对象方法要比其类型更重要
  • 为程序设计带来了很大的灵活性
  • 例:
  • 定义一个驾驶员类Driver
1
2
3
4
5
6
class Driver:
    def __init__(self, moto: Moto):
        self.moto = moto

    def drive(self, speed):
        self.moto.run(speed)
1
2
3
pickup = Pickup(1.8, 3.9, 6, 20)
bobo = Driver(pickup)
bobo.drive(120)
1
本车为 Moto,长1.8,宽3.9,以速度120行驶!
  • 蹦蹦车类Bouncy并不是Moto类的派生类
1
2
3
class Bouncy:
    def run(self, speed):
        print(f"这里一辆蹦蹦车,以速度{speed}行驶")
1
2
3
bouncy = Bouncy()
bobo.moto = bouncy
bobo.drive(2)
1
这里一辆蹦蹦车,以速度2行驶
  • Bouncy的实例虽然不是Moto的实例,但是它们的行为非常相似。这种情况被称为“鸭子类型”

  • If it walks like a duck and it quacks like a duck, then it must be a duck

  • 协议

  • 一组约定的用 于实现特定功能的方法
  • 如果一个类中实现了一个协议中的所有方法,就可以说该类实 现了这个协议,相应地这个类的对象就具备了协议中约定的功能
  • 协议是一种在文档层面的非正式约定,不具有强制性,在语言层面上不 会感知到协议的存在
  • 动态语言中的协议与静态语言(如 Java 和 C++)中接口的概念具有相似的作用

5.5.5 调用基类方法

通过基类名调用基类方法

  • WagonCoachPickup等类的定义中使用了这种方法在__init__中调用基类的__init__方法来初始化基类属性
  • 缺点
  • 增加了代码之间的耦合性
  • 在多重继承的情况下会产生重复调用的问题
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Moto:
    def __init__(self, width, length):
        self.width = width
        self.length = length
        print('执行Moto类 __init__ 方法')

class Wagon(Moto):
    def __init__(self, width, length):
        Moto.__init__(self, width, length)
        print('执行Wagon类 __init__ 方法')

class Coach(Moto):
    def __init__(self, width, length):
        Moto.__init__(self, width, length)
        print('执行Coach类 __init__ 方法')

class Pickup(Wagon, Coach):
    def __init__(self, width, length):
        Wagon.__init__(self, width, length)
        Coach.__init__(self, width, length)
        print('执行Pickup类 __init__ 方法')
1
pickup = Pickup(1.8, 3.9)
1
2
3
4
5
执行Moto类 __init__ 方法
执行Wagon类 __init__ 方法
执行Moto类 __init__ 方法
执行Coach类 __init__ 方法
执行Pickup类 __init__ 方法

通过super调用基类方法

  • super
  • 于创建代理对象或代理类的类
  • 代理对象或代理类会按照方法解析顺序(MRO)来查找要执行的方法
  • 调用方式
  • super(TYPE, OBJ):返回一个代理对象。TYPE是一个类,用于指定MRO中的查找开始位置(不含TYPE自身),OBJ用于指定查找哪个类的MRO。OBJ必须是TYPE的实例,即isinstance(OBJ, TYPE)True
  • super():这种方式只能用于类内部,与super(TYPE, OBJ)一致,只不过TYPEOBJ分别传入的是当前所属的类以及所在方法中的self对象。
  • super(TYPE1, TYPE2):返回一个代理类。TYPE1TYPE2都是类,TYPE1用于指定MRO中的查找开始位置(不含TYPE1自身),TYPE2用于指定查找哪个类的MRO。由于返回的是代理类,所以在调用方法的时候必须考虑方法的时候必须手动传入特殊参数self
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Moto:
    def __init__(self, width, length):
        self.width = width
        self.length = length
        print('执行Moto类 __init__ 方法')

class Wagon(Moto):
    def __init__(self, width, length):
        super().__init__(width, length)
        print('执行Wagon类 __init__ 方法')

class Coach(Moto):
    def __init__(self, width, length):
        super().__init__(width, length)
        print('执行Coach类 __init__ 方法')

class Pickup(Wagon, Coach):
    def __init__(self, width, length):
        super(Pickup, self).__init__(width, length)
        print('执行Pickup类 __init__ 方法')
1
pickup = Pickup(1.8, 3.9)
1
2
3
4
执行Moto类 __init__ 方法
执行Coach类 __init__ 方法
执行Wagon类 __init__ 方法
执行Pickup类 __init__ 方法

5.6 混入*

5.6.1 混入的概念

  • 混入(MinxIn)是有别于继承的另一种代码重用机制,它从另一个角度对现实世界 中事物的关系进行组织和划分,是继承机制的一种有效的补充
  • 混入机制通常利用多重 继承机制实现,实际上混入也是多重继承最主要的应用方式

继承与混入的区别

  • 继承

  • 混入

  • “载货能力”和“载客能力”被称为混入类,“汽 车”类称为具体类,“微卡”、“轿车”等称为被混入类
  • 混入类与被混 入类之间并不是父子关系

  • 混入的优势

  • 混入与继承相比一个重要的优势是能够有效降低代码的复杂程度
  • 在继承关系中,继承的层次会随着新特征的加入而增加,产生冗余的代码,导致程序开发成本和维护成本增加。混入机 制就能够很好地解决这个问题。

  • 混入类应当具备如下特征

  • 与具体类之间不存在逻辑上的依赖关系
  • 包含了一组联系紧密的方法以实现特定的功能
  • 不应该被实例化,混入类的对象没有存在的意义
  • 继承与混入的相同之处在于它们都包含了一组用于实现特定功能的方法;不同之处在于协议仅仅是一种不具有强制性的约定,而混入类中 包含的是具体的、已经被实现了的方法。

5.6.2 Python 中的混入

混入的实现方式

  • Python中混入是用多重继承实现的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Moto:                       # 具体类
    def run(self, speed):
        print(f"本车为{self.__class__.__name__},以速度{speed}行驶!")

class WagonMixIn:                 # 混入类
    def carry_cargo(self):
        print("装载货物!")

class CoachMixIn:                 # 混入类
    def carry_passengers(self):
        print("搭载乘客!")

class Truck(WagonMixIn, Moto):    # 被混入类
    pass

class Car(CoachMixIn, Moto):      # 被混入类
    pass
1
2
car = Car()
car.run(80)
1
本车为Car,以速度80行驶!
1
car.carry_passengers()
1
搭载乘客!
1
2
truck = Truck()
truck.run(60)
1
本车为Truck,以速度60行驶!
1
truck.carry_cargo()
1
装载货物!

在混入类中调用具体类的方法

  • 混入类中定义的功能具有通用性,可以与多种具体类结合个性化地实现其中定义的功能
  • 例如,CarBus混入CoachMixIn之后都具备了载客功能,但是它们的载客功能差异很大
  • 载客功能的实现明显不是混入类CoachMixIn独立实现的,还需要依赖具体类Moto
  • 混入类中一般没有状态信息,必须依赖具体类才能真正实现功能
  • 混入类不应当被实例化的真正原因
  • 混入类中如何调用具体类的方法?
  • 从多重继承的语法来说,混入类和具体类之间是并列的,它们之间没有任何关系
  • Python 的super和方法解析顺序实现

  • super根据参数来确定在 MRO 中的查找方式,其参数传递形式有三种:

  • super(TYPE, OBJ):返回一个代理对象。其中TYPE是一个类,用于指定 MRO 中的 查找开始位置(不含TYPE自身)。OBJ用于指定查找哪个类的 MRO,必须是TYPE的 实例,即isinstance(OBJ, TYPE)必须为True
  • super():这种方式只能用于类内部,与super(TYPE, OBJ)一致,只不过省去了显 式传入的实参TYPEOBJ。解释器默认传入的实参是当前所属的类以及所在方法中 的self对象。
  • super(TYPE1, TYPE2):返回一个代理类,其中TYPE1TYPE2都是类名,TYPE1用 于指定 MRO 中的查找开始位置(不含TYPE1自身),TYPE2用于指定查找哪个类的 MRO。由于返回的是代理类而不是代理对象,因此在调用方法的时候必须手动传入特殊参数self

  • super能够调用基类的__init__方法的原因

  • 以类Wagon为例,super()super(Wagon, self)等价,要查找的是由self确定的类Wagon的 MRO, 从该 MRO 中类Wagon之后开始查找。Wagon之后的第一个类就是Wagon的基类Moto,因 此super().__init__查找到的就是Moto__init__方法

  • 例:在混入类中调用具体类的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Moto:                       # 具体类
    def __init__(self, capacity):
        self.capacity = capacity
    def get_capacity(self):
        return self.capacity

class CoachMixIn:                 # 混入类
    def carry_passengers(self, nums):
        capacity = super().get_capacity()
        if nums > capacity:
            print("人员超载!")
        else:
            print(f"搭载{nums}名乘客!")

class Car(CoachMixIn, Moto):      # 被混入类
    def __init__(self, capacity):
        super().__init__(capacity)
        self.capacity = capacity
1
2
car = Car(5)
car.carry_passengers(10)
1
人员超载!
1
car.carry_passengers(3)
1
搭载3名乘客!
  • 基类列表中混入类必须位于具体类之前

  • 例:利用混入扩展类的功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from collections import defaultdict

class ReadOnlyDictMixin:
    __slots__ = ()
    def __setitem__(self, key, value):
        if key in self:
            old_value = super().__getitem__(key)
            print(f'字典中已存在{key}值,不能修改!')
            value = old_value
        return super().__setitem__(key, value)

class ReadOnlyDict(ReadOnlyDictMixin, defaultdict):
    pass
1
2
3
d = ReadOnlyDict()
d['x'] = 1 
d['x'] = 2
1
字典中已存在x值,不能修改!
1
d.items()
1
dict_items([('x', 1)])
  • 混入类使用原则
  • 混入类使用特殊的命名,一般以MinxIn为后缀
  • 混入类只应当实现功能,不应当有状态信息
  • 不同混入类尽量不要包含名称相同的方法
  • 在定义被混入类时,基类中可以包含多个混入类,但只应当有一个具体类,并且混入类都要位于具体类之前