4. 继承/派生
-
什么是继承/派生
-
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
-
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
-
-
为什么继承/派生
-
继承的目的是延续旧的类的功能
-
派生的目地是在旧类的基础上添加新的功能
-
-
继承/派生的作用
-
用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
-
在不改变基类的代码的基础上改变原有类的功能
-
-
继承/派生名词:
-
基类(base class)/超类(super class)/父类(father class)
-
派生类(derived class)/子类(child class)
-
4.1 单继承
-
单继承的语法:
class 类名(基类名): 语句块
单继承说明:单继承是指派生类由一个基类衍生出来的
4.2 多继承
Python支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
4.3 覆盖 override
-
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖
-
作用:
-
实现和父类同名,但功能不同的方法
-
- v
-
5. 封装 enclosure
封装(Encapsulation) 是一种将数据(属性)和操作数据的方法(方法)绑定在一起的机制。封装的主要目的是隐藏对象的内部状态,并对外部提供有限的访问接口。通过封装,可以防止外部代码直接访问对象的内部数据,从而提高代码的安全性和可维护性。
在 Python 中,封装主要通过以下几种方式实现:
1. 私有属性(Private Attributes)
在 Python 中,没有严格的私有属性,但可以通过命名约定来模拟私有属性。通常,使用单下划线
_
或双下划线__
来表示私有属性。单下划线
_
单下划线前缀表示属性是“受保护的”,建议外部代码不要直接访问。
单下划线前缀
_
是一种约定,而不是语言层面的强制规则。这种约定并没有强制性,外部代码仍然可以直接访问这些属性或方法。 -
示例中虽然_protected_attr被标记为受保护的属性,但还是可以直接访问和修改。
双下划线
__
双下划线前缀表示属性是“私有的”,Python 会对其进行名称改写(name mangling),使其在类外部难以直接访问。
示例运行会报错:
AttributeError: 'MyClass' object has no attribute '__private_attr'
这是因为私有变量被python修改了名称,名称改写的规则是将属性名前面加上类名和一个下划线
_
。假设类MyClass
,其中有一个私有属性__private_attr
,那么 Python 会将这个属性名改写为_MyClass__private_attr
。2. 属性访问器(Getter 和 Setter)
单下划线和双下划线的变量虽然可以直接或间接被访问,但python不建议这么做。
如果需要访问或修改这些属性,应该通过类提供的公共方法
getter
和setter
,来控制对属性的访问和修改。 -
3. 使用
@property
装饰器Python 提供了
@property
装饰器,可以更简洁地实现属性的访问和修改。@property
是 Python 中的一个装饰器,用于将类的方法转换为属性。@property
装饰器用于定义一个只读属性。通过使用@property
,可以将方法的调用方式转换为属性的访问方式,从而使代码更加简洁和易读。如果需要设置属性的值,可以使用
@property.setter
装饰器定义一个可写属性。
@property
通常与@<property_name>.setter
一起使用,以实现属性的读取和写入操作。 -
6. 多态 polymorphic
多态(Polymorphism)是指同一个方法在不同的类中有不同的实现。多态可以分为两种主要类型:静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism)。
6.1 静态多态
静态多态也称为编译时多态(Compile-time Polymorphism),主要通过以下两种方式实现:
6.1.1 方法重载
方法重载是指在同一个类中定义多个同名但参数不同的方法。编译器根据调用时传递的参数类型和数量来决定调用哪个方法。
-
6.1.2 运算符重载
运算符重载(Operator Overloading) 是指通过定义类的魔术方法(Magic Methods),使类的对象支持 Python 的内置运算符(如
+
、-
、*
、/
等)。通过运算符重载,我们可以让自定义类的对象像内置类型(如整数、字符串、列表等)一样使用运算符。 -
6.2 动态多态
动态多态也称为运行时多态(Runtime Polymorphism),主要通过方法重写(Method Overriding)实现。
6.2.1 方法重写
方法重写是指子类重新定义父类中已有的方法,从而在运行时根据对象的实际类型来调用相应的方法。
-
6.2.2 鸭子类型
Python 是一种动态类型语言,支持鸭子类型(Duck Typing)。鸭子类型的核心思想是“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”。这意味着只要对象实现了所需的方法,就可以在代码中使用它,而不需要显式地继承某个基类。
6.2.3 抽象基类
Python 提供了
abc
模块,用于定义抽象基类。抽象基类不能直接实例化,而是作为其他类的模板。子类必须实现抽象基类中定义的抽象方法,否则会引发错误。使用
abc
模块的基本步骤 -
导入模块:导入
abc
模块中的ABC
和abstractmethod
。 -
定义抽象基类:通过继承
ABC
类来定义抽象基类。 -
定义抽象方法:使用
@abstractmethod
装饰器标记抽象方法。 -
实现子类:子类必须实现抽象基类中定义的所有抽象方法,否则无法被实例化。
7. 内建函数重写(了解)
在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作。
然而,这样做通常是不推荐的,因为这可能会导致代码的可读性和可维护性降低,并且可能会引发意外的行为。
对象转字符串函数重写
对象转字符串函数重写方法
-
str() 函数的重载方法:
-
def __str__(self)
-
如果没有
__str__(self)
方法,则返回repr(obj)函数结果代替
-
-
str函数重写示例
class MyNumber: "此类用于定义一个自定义的类,用于演示str函数重写" def __init__(self, value): "构造函数,初始化MyNumber对象" self.data = value def __str__(self): "转换为普通字符串" return "%s" % self.data n1 = MyNumber("一只猫") n2 = MyNumber("一只狗") print("str(n2) ===>", str(n2))
内建函数重写
-
__abs__
abs(obj) 函数调用 -
__len__
len(obj) 函数调用 -
__reversed__
reversed(obj) 函数调用 -
__round__
round(obj) 函数调用 -
内建函数 重写示例
# file : len_overwrite.py class MyList: def __init__(self, iterable=()): self.data = [x for x in iterable] def __repr__(self): return "MyList(%s)" % self.data def __len__(self): print("__len__(self) 被调用!") return len(self.data) def __abs__(self): print("__len__(self) 被调用!") return MyList((abs(x) for x in self.data)) myl = MyList([1, -2, 3, -4]) print(len(myl)) print(abs(myl))
8. super函数
8.1 MRO查找顺序
MRO(Method Resolution Order,方法解析顺序)描述了 Python 在多重继承的情况下,如何查找类的方法和属性。MRO 决定了当一个类继承自多个父类时,Python 如何确定方法和属性的查找顺序。
MRO 的计算规则
Python 使用 C3 线性化算法来计算 MRO。C3 线性化算法确保了以下几点:
子类优先于父类:子类的方法或属性会优先于父类被查找。
父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找。
单调性:如果一个类出现在另一个类的 MRO 中,那么它的父类也应该出现在这个类的 MRO 中。
MRO 的计算方法
MRO 的计算方法可以通过 __mro__
属性或 mro()
方法来查看。
8.2 super()
super() 函数是用于调用父类(超类)的一个方法。
super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
super()
是一个用于调用父类方法的函数,它依赖于 Python 的 MRO 机制。因此,super()
本身并不能解决 MRO 冲突,而是依赖于已经确定的 MRO。
super()
解决多继承问题的关键在于 MRO(方法解析顺序),即 Python 按照某种顺序(通常是从左到右)依次调用继承链中的方法。super()
总是会调用下一个类的 process()
,而不会重复调用同一个类的方法。
使用 super()
时,Python 会动态查找类继承链中的下一个类。通过 MRO,避免了手动调用时可能发生的重复调用。