生命周期
graph TD
A[类定义阶段: class MyClass] --> B[类对象创建]
B --> C1[类属性访问: MyClass.class_attr]
B --> C[实例化阶段: __new__ method]
C --> D[初始化阶段: __init__ method]
D --> E1[属性的设置与获取: __setattr__, __getattr__]
D --> E2[对象的表示: __str__, __repr__]
D --> E3[方法调用]
D --> E4[上下文管理: __enter__, __exit__]
D --> E5[对象比较: __eq__, __lt__]
D --> E6[对象的计算: __add__, __mul__]
E1 --> F
E2 --> F
E3 --> F
E4 --> F
E5 --> F
E6 --> F
F[对象销毁: del obj or GC] --> G[__del__ method]
G --> H[垃圾回收: Cleanup by Python GC]
- 类定义阶段:在这个阶段定义类的方法和属性。
- 类对象创建:当 Python 读取到类定义时,它会创建一个类对象。
- 类属性访问:在实例化之前,可以访问类的静态和类属性。
- 实例化阶段:使用类名创建对象时,首先调用
__new__
方法来分配内存。 - 初始化阶段:紧随其后,
__init__
方法会被调用,对新对象进行初始化。 - 属性的设置与获取:在对象的生命周期中,你可以使用
__getattr__
,__setattr__
,__getattribute__
等特殊方法来控制属性的访问。 - 对象的表示:当尝试显示对象时(例如,通过 print),
__str__
和__repr__
方法可以被调用。 - 方法调用:可以调用对象的方法。
- 上下文管理:使用
with
语句时,对象的__enter__
和__exit__
方法会被调用。 - 对象比较:使用比较操作符时(如==或>),
__eq__
,__lt__
,__le__
等特殊方法会被调用。 - 对象的计算:如加法或乘法等运算符,会调用
__add__
,__mul__
等特殊方法。 - 对象销毁:当对象的引用计数减少到零或者被明确销毁(使用
del
语句)时,__del__
方法会被调用。 - 垃圾回收:Python 的垃圾回收器会识别循环引用,并在适当的时候销毁相关对象。
特殊方法
Python 中的类包含了许多特殊方法,它们经常被称为魔术方法(magic methods)或者双下方法(dunder methods),因为它们的名字都是以双下划线开始和结束的。这些特殊方法为 Python 的对象提供了许多内置的功能,如算术运算、迭代和字符串表示。
魔术方法是 Python 类的特殊方法,它们定义了许多 Python 中基本的操作。例如,当你为一个对象定义了 __add__
方法时,这个对象就可以使用 +
运算符。这些方法的名称都是以两个下划线开始和结束,这也是为什么它们被称为双下方法的原因。
基础方法
__init__
__init__
方法是类的构造函数,当我们创建类的实例时,__init__
方法会被自动调用。我们可以在 __init__
方法中初始化实例的属性。
__del__
__del__
方法是类的析构函数,当一个实例被销毁时(例如被垃圾回收器回收时),__del__
方法会被自动调用。注意,我们通常不需要在 __del__
方法中做清理工作,Python 的垃圾回收器会自动清理对象的资源。
__str__
__str__
方法返回一个表示该对象的字符串,这个字符串通常用于给用户看。当我们调用 str()
或 print()
时,会使用 __str__
的返回值。
注意,__repr__
和 __str__
的区别在于,__repr__
更侧重于开发,而 __str__
更侧重于用户。
__repr__
__repr__
方法返回一个表示该对象的官方字符串,这个字符串通常可以被 eval()
执行来重新得到这个对象。如果我们没有定义 __str__
方法,那么在调用 str()
或 print()
时也会使用 __repr__
的返回值。
__format__
__format__
方法定义了当我们调用 format()
或使用格式化字符串(f-string)时的行为。format_spec
是一个格式说明符,它是在格式化字符串中 :
后面的部分。
数学运算
比较运算的魔术方法允许类的实例之间进行比较。例如,__eq__
定义了对象的等于操作。这些方法的使用可以使您的类实例支持标准的比较操作符,如 ==
, !=
, +
, -
, %
, 和 @
。类似地,可以为其他数学运算符定义其他魔术方法。
运算符 | 对应的魔术方法 | 描述 |
---|---|---|
== | __eq__ | 等于 |
!= | __ne__ | 不等于 |
< | __lt__ | 小于 |
<= | __le__ | 小于或等于 |
> | __gt__ | 大于 |
>= | __ge__ | 大于或等于 |
+ | __add__ | 加法 |
- | __sub__ | 减法 |
* | __mul__ | 乘法 |
/ | __truediv__ | 真除 |
// | __floordiv__ | 整除 |
% | __mod__ | 取模 |
** | __pow__ | 乘方 |
@ | __matmul__ | Python 3.5+ 矩阵乘法 |
容器方法
__len__
__len__
方法定义了 len()
的返回值。它应该返回一个整数,表示对象包含的元素的个数。
__getitem__
__getitem__
方法定义了使用索引访问元素的行为。key
是索引。
__setitem__
__setitem__
方法定义了使用索引设置元素的行为。key
是索引,value
是我们试图设置的值。
__delitem__
__delitem__
方法定义了使用索引删除元素的行为。key
是索引。
__iter__
、__next__
迭代器协议的魔术方法允许对象支持迭代,这意味着您可以在对象上使用 for
循环。为了使一个对象可迭代,您需要定义 __iter__
和 __next__
两个魔术方法。
-
__iter__
返回对象本身,它应该返回一个实现了__next__
的迭代器对象。 -
__next__
方法返回序列中的下一个值。如果所有项都返回了,那么它应该引发一个StopIteration
异常来通知迭代的完成。
在上述 Counter
类中,我们定义了一个简单的迭代器,它从 start
开始,每次迭代增加 1,直到 end
为止。for
循环通过调用 __iter__
来获取迭代器对象,并在每次迭代中调用 __next__
,直到捕获 StopIteration
异常为止。
属性访问
__dir__
__dir__
方法返回类中定义的属性、方法等的列表。它对内置的 dir()
函数的行为进行重载。
__getattr__
当尝试访问一个不存在的属性时,__getattr__
方法会被调用。name
是试图访问的属性名称。
值得注意的是,__getattr__
只有在所请求的属性不存在时才会被调用。
__setattr__
每当尝试设置一个属性值时,__setattr__
都会被调用,不论该属性是否存在。name
是试图设置的属性的名称,而 value
是试图赋给该属性的值。
__delattr__
当试图删除一个属性时,__delattr__
方法会被调用。name
是试图删除的属性的名称。
__getattribute__
每次尝试访问一个属性时,__getattribute__
方法都会被调用,无论该属性是否存在。
重要的是,如果 __getattribute__
被定义,那么 __getattr__
不会被调用,因为 __getattribute__
的优先级更高。*
上下文管理
__enter__
、__exit__
当使用 with
语句进入上下文管理时,__enter__
方法会被调用。它应该返回上下文管理器对象本身或其他与上下文相关的对象。
当 with
语句块结束时,__exit__
方法会被调用。它接收三个参数:exc_type
、exc_val
和 exc_tb
,分别代表异常类型、异常值和异常回溯。如果 with
语句块中没有发生异常,这三个参数的值都为 None
。
如果在 with
语句块中引发了异常,__exit__
方法可以选择处理这个异常(例如记录日志)并返回 True
来阻止异常向外传播,或者返回 False
(或 None
)让异常继续向外传播。
上下文管理器是一种非常强大的工具,特别是当涉及到需要设置和清理资源的任务时,例如文件 I/O、网络连接或数据库连接。
描述符
描述符是实现了描述符协议的对象。描述符协议由 __get__
、__set__
和 __delete__
方法组成。描述符用于创建那些需要特殊行为的对象属性,例如类型检查或只读属性。
__get__
__get__
方法定义了在获取属性时应执行的行为。当试图获取属性值时,这个方法会被调用。
__set__
__set__
方法定义了在设置属性值时应执行的行为。当试图给属性赋值时,这个方法会被调用。
__delete__
__delete__
方法定义了当删除属性时应执行的行为。当试图删除属性时,这个方法会被调用。
使对象可调用
**在 Python 中,函数是一类对象,可以调用它们。**但是,您知道您也可以使自己的对象表现得像函数一样吗?通过定义 __call__
魔术方法,您可以使类的实例表现得像函数,从而允许对它们进行调用。
__call__
当实例作为函数被“调用”时,__call__
方法就会被执行。这提供了一种优雅的方式来使用对象,同时保持其对象性质。通过这种方式,您的对象不仅可以表示数据,还可以表现得像函数,这增加了编码的灵活性和创造性。
值比较
__hash__
__hash__
方法返回对象的哈希值。哈希值通常用于字典的键值和其他需要快速查找的数据结构中。如果一个对象是可变的,通常最好不要实现此方法。如果对象定义了 __eq__
方法并且是不可变的,则通常也应定义此方法。
__bool__
__bool__
方法用于实现 bool()
内置函数的调用。当我们调用 bool()
函数或使用对象在条件语句(例如 if obj:
)中作为条件时,会调用此方法。如果 __bool__
没有被定义,__len__
会被调用(如果已定义)。如果两者都未定义,所有实例都默认为 True
。
类型转换
__int__
__int__
方法允许将一个对象转换为整数。当使用 int()
内置函数时,如果对象实现了此方法,会被调用。
__float__
__float__
方法允许将一个对象转换为浮点数。当使用 float()
内置函数时,如果对象实现了此方法,会被调用。
__complex__
__complex__
方法允许将一个对象转换为复数。当使用 complex()
内置函数时,如果对象实现了此方法,会被调用。
__bytes__
__bytes__
方法定义了当我们调用 bytes()
时的行为。它应该返回一个字节串。
类和静态方法
在 Python 中,类是一个创建对象的蓝图。对象则是基于类定义的实例。默认情况下,在类内部定义的方法是实例方法。
实例方法
实例方法的第一个参数是 self
,代表类的实例对象。它可以访问和修改与实例相关的属性和方法。实例方法只能由其实例对象调用。
类方法
类方法使用 @classmethod
装饰器定义。其第一个参数是 cls
,代表类本身。类方法既可以由类直接调用,也可以被其实例调用。
静态方法
静态方法使用 @staticmethod
装饰器定义。它不需要传递 self
或 cls
参数。静态方法不能访问或修改类或实例的属性和方法。它仅仅与它所在的类相关,但不需要访问类的特性。
类方法 Vs 静态方法
类型 | 用途 | 优点 | 缺点 |
---|---|---|---|
类方法 | 访问/修改类属性,方法继承于子类 | 可访问/修改类属性,适用于继承 | 不能访问实例特有的属性 |
静态方法 | 不需访问实例/类数据的操作,与类相关但不需访问类或实例 | 无需实例化,代码组织清晰 | 不能访问类和实例的属性或方法 |
在面向对象编程中,合理地使用实例方法、类方法和静态方法可以使代码更有组织性,更易于维护。
属性
@property
装饰器
在 Python 中,@property
装饰器使我们能够将类中的方法用作属性,从而实现对属性的控制。它可以用于确保属性的读取和设置遵循某种特定的逻辑。
使用 Setter 和 Getter 方法
Setter 和 Getter 在 Python 中用于控制属性的访问和赋值。
@<property_name>.setter
在定义了属性的 getter 方法后,我们可以使用 @<property_name>.setter
装饰器定义相应的 setter 方法,以控制该属性的赋值逻辑。
@<property_name>.deleter
除了设置和获取属性,我们还可以定义如何删除属性。
属性保护
在 Python 中,我们通常使用下划线来表示属性应该是私有的或受保护的。
- 受保护的属性: 通常使用单下划线前缀
_
来定义,例如_name
。这只是一个约定,表示这个属性是为类内部使用的,但外部仍然可以访问。 - 私有属性: 使用双下划线前缀
__
来定义,例如__name
。Python 会对其进行名称修饰,使得在类的外部更难直接访问。
__slots__
限制动态属性的添加
为了提高性能和内存效率,Python 允许在类定义中使用 __slots__
来限制可以添加到对象的属性。这通常在需要创建大量对象时很有用。