1.继承/派生
-
什么是继承/派生
-
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
-
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
-
-
为什么继承/派生
-
继承的目的是延续旧的类的功能
-
派生的目地是在旧类的基础上添加新的功能
-
-
继承/派生的作用
-
用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
-
在不改变基类的代码的基础上改变原有类的功能
-
-
继承/派生名词:
-
基类(base class)/超类(super class)/父类(father class)
-
派生类(derived class)/子类(child class)
-
1.1 单继承
-
单继承的语法:
-
class 类名(基类名):
语句块 -
单继承说明:单继承是指派生类由一个基类衍生出来的
-
class Human: # 人类的共性 def say(self, what): # 说话 print("说:", what) def walk(self, distance): # 走路 print("走了", distance, "公里") class Student(Human): def study(self, subject): # 学习 print("学习:", subject) class Teacher(Human): def teach(self, language): print("教:", language) h1 = Human() h1.say("天气真好!") h1.walk(5) s1 = Student() s1.walk(4) s1.say("感觉有点累") s1.study("python") t1 = Teacher() t1.teach("面向对象") t1.walk(6) t1.say("一会吃点什么好呢")
Python支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
-
# 类定义 class people: # 定义基本属性 name = '' age = 0 # 定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 # 定义构造方法 def __init__(self, n, a, w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" % (self.name, self.age)) # 另一个类,多继承之前的准备 class speaker(): topic = '' name = '' def __init__(self, n, t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic)) # 多继承 class sample(people, speaker): def __init__(self, n, a, w, t): people.__init__(self, n, a, w) speaker.__init__(self, n, t) test = sample("Tim", 25, 80, "Python") test.speak() # 方法名同,默认调用的是在括号中参数位置排前父类的方法
4.2 多继承
Python支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
-
# 类定义 class people: # 定义基本属性 name = '' age = 0 # 定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 # 定义构造方法 def __init__(self, n, a, w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" % (self.name, self.age)) # 另一个类,多继承之前的准备 class speaker: topic = '' name = '' def __init__(self, n, t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic)) # 多继承 class sample(people, speaker): def __init__(self, n, a, w, t): people.__init__(self, n, a, w) speaker.__init__(self, n, t) test = sample("Tim", 25, 80, "Python") test.speak() # 方法名同,默认调用的是在括号中参数位置排前父类的方法
4.3 覆盖 override
-
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖
-
作用:
-
实现和父类同名,但功能不同的方法
-
-
覆盖示例
class A: def work(self): print("A.work 被调用!") class B(A): '''B类继承自A类''' def work(self): print("B.work 被调用!!!") pass b = B() b.work() # 请问调用谁? B a = A() a.work() # 请问调用谁? A
2. 封装 enclosure
封装(Encapsulation) 是一种将数据(属性)和操作数据的方法(方法)绑定在一起的机制。封装的主要目的是隐藏对象的内部状态,并对外部提供有限的访问接口。通过封装,可以防止外部代码直接访问对象的内部数据,从而提高代码的安全性和可维护性。
在 Python 中,封装主要通过以下几种方式实现:
1. 私有属性(Private Attributes)
在 Python 中,没有严格的私有属性,但可以通过命名约定来模拟私有属性。通常,使用单下划线
_
或双下划线__
来表示私有属性。单下划线
_
单下划线前缀表示属性是“受保护的”,建议外部代码不要直接访问。
单下划线前缀
_
是一种约定,而不是语言层面的强制规则。这种约定并没有强制性,外部代码仍然可以直接访问这些属性或方法。 -
class A: def __init__(self,value): self._value=value a=A(5) a._value=2 print(a._value)
双下划线
__
双下划线前缀表示属性是“私有的”,Python 会对其进行名称改写(name mangling),使其在类外部难以直接访问。
-
class MyClass: def __init__(self): self.__private_attr = 43 obj = MyClass() print(obj.__private_attr)
示例运行会报错:
AttributeError: 'MyClass' object has no attribute '__private_attr'
这是因为私有变量被python修改了名称,名称改写的规则是将属性名前面加上类名和一个下划线
_
。假设类MyClass
,其中有一个私有属性__private_attr
,那么 Python 会将这个属性名改写为_MyClass__private_attr
。 -
class MyClass: def __init__(self): self.__private_attr = 43 obj = MyClass() print(obj._MyClass__private_attr)
obj._MyClass__private_attr:直接访问python修改后的私有变量名称可以成功,但不建议这么访问,主要是为了防止子类意外覆盖父类的私有属性。
2. 属性访问器(Getter 和 Setter)
单下划线和双下划线的变量虽然可以直接或间接被访问,但python不建议这么做。
如果需要访问或修改这些属性,应该通过类提供的公共方法
getter
和setter
,来控制对属性的访问和修改 -
class MyClass: def __init__(self): self._private_attr = 43 def get_attr(self): return self._private_attr def set_attr(self, attr): self._private_attr = attr a = MyClass() print(a.get_attr()) print(a.set_attr(12)) print(a.get_attr()) 43 None 12
3. 使用
@property
装饰器Python 提供了
@property
装饰器,可以更简洁地实现属性的访问和修改。@property
是 Python 中的一个装饰器,用于将类的方法转换为属性。@property
装饰器用于定义一个只读属性。通过使用@property
,可以将方法的调用方式转换为属性的访问方式,从而使代码更加简洁和易读。如果需要设置属性的值,可以使用
@property.setter
装饰器定义一个可写属性。@property
通常与@<property_name>.setter
一起使用,以实现属性的读取和写入操作。 -
class A: def __init__(self,value): self._protec_value=value @property def propet(self): return self._protec_value @propet.setter def propet(self,value): self._protec_value=value a = A(10) print(a.propet) a.propet = 18 print(a.propet)
3. 多态 polymorphic
多态(Polymorphism)是指同一个方法在不同的类中有不同的实现。多态可以分为两种主要类型:静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism)。
3.1 静态多态
静态多态也称为编译时多态(Compile-time Polymorphism),主要通过以下两种方式实现:
3.1.1 方法重载
方法重载是指在同一个类中定义多个同名但参数不同的方法。编译器根据调用时传递的参数类型和数量来决定调用哪个方法。
-
class calculeter: def add(self,a,b): return a+b def add(self,a,b,c): return a+b+c c = calculeter() print(c.add(10,20)) # 在 Python 中,方法重载并不直接支持,因为 Python 是动态类型语言 # 但可以通过默认参数或可变参数来模拟方法重载 class calculeter: def add(self,a,b,c=0): return a+b+c c = calculeter() print(c.add(10,20,30))
3.1.2 运算符重载
运算符重载(Operator Overloading) 是指通过定义类的魔术方法(Magic Methods),使类的对象支持 Python 的内置运算符(如
+
、-
、*
、/
等)。通过运算符重载,我们可以让自定义类的对象像内置类型(如整数、字符串、列表等)一样使用运算符。方法名 运算符 说明 __add__(self, rhs)
+ 加法 __sub__(self, rhs)
- 减法 __mul__(self, rhs)
* 乘法 __truediv__(self, rhs)
/ 除法 __floordiv__(self, rhs)
// 整除 __mod__(self, rhs)
% 取模(求余) __pow__(self, rhs)
** 幂
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __str__(self):
dic = {'x': self.x, 'y': self.y}
return f'{dic}'
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3)
{'x': 4, 'y': 6}
3.2 动态多态
动态多态也称为运行时多态(Runtime Polymorphism),主要通过方法重写(Method Overriding)实现。
3.2.1 方法重写
方法重写是指子类重新定义父类中已有的方法,从而在运行时根据对象的实际类型来调用相应的方法。
示例:假设基类为Animal,拥有一个speak方法,Dog和Cat分别是Animal的子类,继承Animal的方法
class Animal:
def speak(self):
return '通用'
class Dog(Animal):
def speak(self):
return '狗叫'
class Cat(Animal):
def speak(self):
return '猫叫'
dog = Dog()
print(dog.speak())
狗叫
3.2.3 抽象基类
Python 提供了 abc
模块,用于定义抽象基类。抽象基类不能直接实例化,而是作为其他类的模板。子类必须实现抽象基类中定义的抽象方法,否则会引发错误
抽象基类的作用
定义接口规范:
抽象基类可以定义一组方法(接口),要求所有子类必须实现这些方法。
这样可以确保所有子类都遵循相同的接口规范,避免遗漏或错误。
强制子类实现:
如果子类没有实现抽象基类中定义的所有抽象方法,Python 会在实例化时抛出 TypeError。
这种机制可以避免子类忘记实现某些关键方法。
提高代码可读性和可维护性:
抽象基类明确地告诉开发者,哪些方法是必须实现的,哪些是可选的。
这样可以让代码的结构更清晰,减少潜在的错误。
支持类型检查:
使用抽象基类可以通过 isinstance() 和 issubclass() 检查对象是否符合特定的接口规范。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# 多态的体现
def animal_sound(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!
4. super函数
4.1 MRO查找顺序
MRO(Method Resolution Order,方法解析顺序)描述了 Python 在多重继承的情况下,如何查找类的方法和属性。MRO 决定了当一个类继承自多个父类时,Python 如何确定方法和属性的查找顺序。
MRO 的计算规则
Python 使用 C3 线性化算法来计算 MRO。C3 线性化算法确保了以下几点:
-
子类优先于父类:子类的方法或属性会优先于父类被查找。
-
父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找。
-
单调性:如果一个类出现在另一个类的 MRO 中,那么它的父类也应该出现在这个类的 MRO 中。
MRO 的计算方法
MRO 的计算方法可以通过 __mro__
属性或 mro()
方法来查看。
class A:
def method(self):
print("A.method")
class B(A):
def method(self):
print("B.method")
class C(A):
def method(self):
print("C.method")
class D(B, C):
pass
# 查看 D 类的 MRO
print(D.__mro__)
#输出:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
class A:
def method(self):
print("A.method")
class B(A):
def method(self):
print("B.method")
class C(A):
def method(self):
print("C.method")
class D(B, C):
def method(self):
print("D.method")
class E(C, B):
def method(self):
print("E.method")
class F(D, E):
pass
# 查看 F 类的 MRO
print(F.__mro__)
-
D
的 MRO:-
D
继承自B
和C
。 -
B
继承自A
。 -
C
继承自A
。 -
因此,
D
的 MRO 顺序是D -> B -> C -> A -> object
。
-
-
E
的 MRO:-
E
继承自C
和B
。 -
C
继承自A
。 -
B
继承自A
。 -
因此,
E
的 MRO 顺序是E -> C -> B -> A -> object
。
-
-
F
的 MRO:-
F
继承自D
和E
。 -
D
的 MRO 顺序是D -> B -> C -> A -> object
。 -
E
的 MRO 顺序是E -> C -> B -> A -> object
。
-
问题原因
-
D
和E
的继承关系导致了循环依赖,无法满足 C3 线性化算法的单调性、一致性和局部优先级规则。 -
具体来说,
D
的 MRO 顺序是D -> B -> C -> A -> object
,而E
的 MRO 顺序是E -> C -> B -> A -> object
。 -
这导致了
F
无法创建一致的 MRO,因为B
和C
的顺序在D
和E
中不一致。
在这种情况下,Python无法创建一个一致的方法解析顺序(MRO),解决办法:类D和E继承B、C的顺序一致,即:
4.2 super()
super() 函数是用于调用父类(超类)的一个方法。
super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
super()
是一个用于调用父类方法的函数,它依赖于 Python 的 MRO 机制。因此,super()
本身并不能解决 MRO 冲突,而是依赖于已经确定的 MRO。
super()
解决多继承问题的关键在于 MRO(方法解析顺序),即 Python 按照某种顺序(通常是从左到右)依次调用继承链中的方法。super()
总是会调用下一个类的 process()
,而不会重复调用同一个类的方法。
使用 super()
时,Python 会动态查找类继承链中的下一个类。通过 MRO,避免了手动调用时可能发生的重复调用。
super() 方法的语法:
super()
的无参数形式:
在子类方法中可以使用super().add()调用父类中已被覆盖的方法
class A:
def add(self, x):
y = x+1
print(y)
class B(A):
def add(self, x):
print("子类方法")
super().add(x)
b = B()
b.add(2) # 3