OOP
类和实例基础
Python 允许在运行时动态地为某个实例添加、修改或删除属性。
1 | class Dog: |
在Python中,实例的变量名如果以**__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
1
2
3
4
5
6
7
8
9
10class Cat:
def __init__(self, name):
self.__name = name # 私有变量
def get_name(self):
return self.__name # 通过方法访问私有变量
cat1 = Cat("Whiskers")
print(cat1.get_name()) # 输出: Whiskers
print(cat1.__name) # 报错: AttributeError: 'Cat' object has no attribute '__name'_ClassName__varname
的方式访问,但这不推荐这样做,因为它违反了封装的原则。例如上面的例子中,其实可以通过
cat1._Cat__name 来访问私有变量 __name。
实例属性和类属性
正如我们前面所说, 由于Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量动态绑定,或者通过self变量(在__init__函数中):
1 | class Dog: |
在Python中,实例属性是属于某个具体实例的属性,而类属性是属于类本身的属性,所有实例共享同一个类属性。
1 | class Dog: |
如果你在实例中创建了和类属性同名的属性,那么实例属性会覆盖类属性. 因此, 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字.
1 | dog1.species = "Canis lupus" |
获取对象信息
Python 提供了内置函数 type()
来获取对象的类型,isinstance()
来检查对象是否是某个类的实例(其实也是检查类型),dir()
来列出对象的所有属性和方法。
1 | class Animal: |
注意这里的 dir()
函数返回的是一个列表,包含了对象的所有属性和方法,包括内置的和自定义的,
其中 __ 开头和结尾的方法是 Python
的魔法方法,用于实现特定的行为, 而没有 __
的是普通方法, 例如speak() 方法。
比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
1 | class MyList: |
getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
- getattr(obj, 'attr'):获取对象 obj 的属性
attr 的值。 -
setattr(obj, 'attr', value):设置对象 obj
的属性 attr 的值为 value。 -
hasattr(obj, 'attr'):检查对象 obj 是否有属性
attr,返回布尔值。 1 | class Person: |
鸭子类型
在Python中,鸭子类型(Duck Typing)是一种动态类型系统的概念,它强调的是对象的行为(方法和属性)而不是对象的实际类型。
换句话说,如果一个对象看起来像鸭子、叫起来像鸭子,那么它就可以被当作鸭子来对待。也就是说,只要一个对象实现了某个方法或属性,就可以被视为具有该行为,而不需要显式地继承某个类或实现某个接口。
1 | class Duck: |
这里的 make_it_quack
函数接受任何对象,只要该对象有 quack
方法,就可以调用它,而不关心该对象的实际类型, 这就是鸭子类型的体现。
多继承和MixIn
与C++一样,Python也支持多继承,但它通过一种非常明确且可预测的方式——MRO(方法解析顺序)——解决了多继承中的二义性问题,尤其是菱形继承问题。
Python的多继承
在Python中,一个类可以同时从多个父类继承,从而获得所有父类的属性和方法。
1 | class Bird: |
而当遇到菱形继承时,Python并不会像C++那样需要开发者使用virtual关键字来解决。相反,Python使用 C3线性化算法 来计算出一个确定的方法解析顺序(Method Resolution Order, MRO)。
MRO是一个列表,它定义了当调用一个方法时,Python解释器查找该方法的顺序。你可以通过类的 mro 属性或 mro() 方法来查看它。
1 | class A: |
1 | print(D.mro()) |
MRO保证了无论继承结构多复杂,方法的查找路径都是唯一且确定的,从而优雅地解决了二义性问题。
MixIn 设计模式
虽然多继承很强大,但如果滥用,会使类的层次结构变得混乱且难以维护。为了以一种更可控、更清晰的方式利用多继承,Python社区广泛采用 MixIn 设计模式。
MixIn (或 Mix-in) 是一个类,它包含了一组特定的、可重用的功能,旨在通过多继承的方式“混入”到其他类中,为这些类添加某些功能,而不是为了建立 “is-a” (是一个) 的父子关系。我们可以把MixIn看作是一个功能插件。
MixIn类通常有以下特点:
- 功能单一、职责明确:一个MixIn类通常只提供一种特定的功能,例如日志记录、序列化、对象表示等。
- 不用于实例化:MixIn类通常不应该被直接实例化。它存在的意义就是被其他类继承。
- 通常无 init 构造函数:为了避免与主类的构造函数发生冲突,MixIn通常**不定义自己的__init__方法**。如果需要初始化,也应确保它能与super()链良好协作。
- 命名约定:为了清晰起见,MixIn类的命名通常以后缀 Mixin 结尾,例如 LoggingMixin、JSONMixin。
假设我们有几个不同的类,我们都希望它们能方便地将自身属性转换为字典或JSON格式。我们可以为此创建一个 SerializationMixin。
1 | import json |
这里 SerializationMixin 提供了 to_dict() 和 to_json() 两个方法。它本身不关心操作的是什么对象,只负责通用的序列化逻辑。
SerializableBook 通过继承 SerializationMixin 和 Book,既拥有了Book的属性和逻辑,又“免费获得”了序列化的能力。
注意继承顺序很重要:我们将 SerializationMixin 放在前面。根据MRO,如果Book中也有一个to_dict方法,那么SerializationMixin中的版本会优先被调用。