定义
Python 中的特殊方法(也称为“魔术方法”或“dunder 方法”,因为它们的名称前后有两个下划线,如 __init__
)是 Python 类的一部分,用来定义对象的某些行为。这些方法在特定情况下被自动调用。它们可以帮助我们实现一些内置功能,比如运算符重载、对象创建、字符串表示等。
常见的特殊方法
Python 中的魔术方法可以按照不同的类别进行划分,根据它们的功能和触发场景。下面将这些方法分为几大类,并逐一介绍:
对象的创建与销毁
__new__(cls, ...)
:作用:控制对象的创建过程,在对象创建之前被调用。通常不需要重写,除非需要自定义对象的创建逻辑。__new__
返回类的实例。__init__(self, ...)
:作用:对象初始化方法,创建对象后立即调用,用于设置对象属性。__del__(self)
:作用:对象销毁方法,当对象被垃圾回收时调用。可以用来执行清理操作,但不常使用。(在GC之前,Python会调用这个对象的__del__()方法完成一些终止化工作。如果没有__del__()方法,那么Python不做特殊的处理。)
对象表示
__str__(self)
:- 作用:定义对象的用户友好字符串表示,当调用
print()
或str()
时被触发。
- 作用:定义对象的用户友好字符串表示,当调用
__repr__(self)
:- 作用:定义对象的官方字符串表示,通常用于调试。通过
repr()
或在解释器中直接调用对象时触发。
- 作用:定义对象的官方字符串表示,通常用于调试。通过
__format__(self, format_spec)
:- 作用:定义对象的格式化输出,当使用
format()
函数或f-string
时调用。
- 作用:定义对象的格式化输出,当使用
__bytes__(self)
:- 作用:定义对象的字节表示,当调用
bytes()
函数时触发。
- 作用:定义对象的字节表示,当调用
属性访问与管理
__getattr__(self, name)
作用:当你试图访问的属性不存在时,
__getattr__
会被调用。它可以用于提供默认属性值或动态生成属性。注意:
__getattr__
只在属性不存在时才被调用。如果属性存在,Python 会直接访问它,而不会调用__getattr__
。
示例:
1 |
|
__getattribute__(self, name)
作用:
__getattribute__
是更底层的属性访问控制方法。无论属性是否存在,每次访问对象属性时都会调用它。一般不重写。注意:由于
__getattribute__
在所有属性访问时都会触发,因此使用时要非常小心,以避免无限递归(需要在函数内使用super()
或object.__getattribute__()
来访问属性)。
示例:
1 |
|
在这个例子中,每次我们尝试访问属性时,都会打印出属性名称,并且通过 super().__getattribute__(name)
确保正常返回属性值。
__setattr__(self, name, value)
作用:每当你尝试设置对象的属性值时,
__setattr__
会被调用。你可以使用它来控制属性赋值行为,比如验证、修改或记录赋值操作。注意:为了避免无限递归,在
__setattr__
内部你需要使用super().__setattr__(name, value)
或object.__setattr__(self, name, value)
来真正设置属性值,而不能直接使用self.name = value
,否则会再次调用__setattr__
。
示例:
1 |
|
__delattr__(self, name)
作用:当你使用
del
删除对象的属性时,__delattr__
会被调用。你可以控制是否允许删除某些属性或在删除时执行一些操作。注意:与
__setattr__
类似,要避免递归调用,你需要使用super().__delattr__(name)
来删除属性,而不是直接del self.name
。
示例:
1 |
|
在这个例子中,当我们删除属性 existing_attr
时,__delattr__
会打印出属性名称,并通过 super()
调用父类方法实际删除属性。
完整示例:属性访问控制器
结合 __getattr__
、__setattr__
和 __delattr__
,我们可以实现一个属性访问控制器,动态生成属性并限制某些属性的删除。
示例:
1 |
|
property()
与 @property
语法糖
除了上述魔术方法,Python 还提供了内置的 property()
函数和 @property
装饰器,用于简化属性访问和管理。这允许我们定义“getter”、“setter”和“deleter”方法。
示例:
1 |
|
容器行为(列表、字典等)
在 Python 中,容器类(如列表、字典等)的行为可以通过魔术方法来定制。通过实现这些魔术方法,可以让自定义对象表现得像标准容器一样,支持索引访问、迭代、元素添加和删除等操作。
下面将详细介绍 Python 中与容器行为相关的魔术方法,并附上代码示例。
__len__(self)
- 作用:定义容器的长度,当使用
len()
函数时会调用__len__
,它返回容器中元素的个数。
示例:
1 |
|
__getitem__(self, key)
- 作用:定义通过键或索引获取元素的行为。可以让对象表现得像列表、字典或其他可索引的容器。该方法是实现切片的关键。
示例:
1 |
|
在这个例子中,__getitem__
使得 MyContainer
对象可以像列表一样通过索引访问元素。
__setitem__(self, key, value)
- 作用:定义通过键或索引设置元素的行为。允许对象像列表或字典一样修改内部数据。
示例:
1 |
|
在这个例子中,__setitem__
允许通过索引修改容器中的值。
__delitem__(self, key)
- 作用:定义通过键或索引删除元素的行为。让对象可以像列表或字典一样删除元素。
示例:
1 |
|
在这个例子中,__delitem__
允许通过索引删除容器中的元素。
__iter__(self)
和 __next__(self)
作用:定义对象的迭代行为。通过实现
__iter__()
和__next__()
,可以让对象像列表一样进行迭代。__iter__()
返回一个迭代器对象,__next__()
返回容器的下一个元素。注意:通常
__iter__
返回的是对象本身,而__next__
负责返回每个元素,迭代结束时抛出StopIteration
。
示例:
1 |
|
输出:
1 |
|
在这个例子中,__iter__
和 __next__
方法使 MyContainer
支持迭代器协议,允许我们使用 for
循环遍历对象。
__contains__(self, item)
- 作用:定义
in
运算符的行为,用于检查某个元素是否在容器中。__contains__
返回布尔值True
或False
。
示例
1 |
|
在这个例子中,__contains__
使得我们可以使用 in
运算符来检查 MyContainer
对象中是否包含某个元素。
__reversed__(self)
- 作用:定义当调用
reversed()
函数时的行为,使容器能够以相反顺序进行迭代。
示例:
1 |
|
输出:
1 |
|
在这个例子中,__reversed__
允许我们使用 reversed()
函数以相反顺序迭代 MyContainer
对象中的元素。
运算符重载
Python 中的运算符可以通过魔术方法进行重载,以便在类实例上执行这些运算符操作。常见的运算符及其对应的魔术方法包括:
算术运算符:
- **
__add__(self, other)
**:+
运算符 - **
__sub__(self, other)
**:-
运算符 - **
__mul__(self, other)
**:*
运算符 - **
__truediv__(self, other)
**:/
运算符 - **
__floordiv__(self, other)
**://
运算符 - **
__mod__(self, other)
**:%
运算符 - **
__pow__(self, other)
**:**
运算符
- **
比较运算符:
- **
__eq__(self, other)
**:==
- **
__ne__(self, other)
**:!=
- **
__lt__(self, other)
**:<
- **
__le__(self, other)
**:<=
- **
__gt__(self, other)
**:>
- **
__ge__(self, other)
**:>=
- **
位运算符:
- **
__and__(self, other)
**:&
- **
__or__(self, other)
**:|
- **
__xor__(self, other)
**:^
- **
__lshift__(self, other)
**:<<
- **
__rshift__(self, other)
**:>>
- **
一元运算符:
- **
__neg__(self)
**:-
(取负) - **
__pos__(self)
**:+
(正号) - **
__invert__(self)
**:~
(按位取反)
- **
上下文管理
__enter__(self)
和 **__exit__(self, exc_type, exc_val, exc_tb)
**:- 作用:实现上下文管理协议,使对象可以与
with
语句一起使用。__enter__
在进入上下文时调用,__exit__
在退出上下文时调用。
1
2
3
4
5
6
7class MyContext:
def __enter__(self):
print("Entering context")
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting context")
with MyContext():
print("Inside the context")- 作用:实现上下文管理协议,使对象可以与
调用与调用相关
**
__call__(self, ...)
**:- 作用:使对象可以像函数一样被调用。
1
2
3
4
5class CallableClass:
def __call__(self, x):
return x * 2
obj = CallableClass()
print(obj(5)) # 输出: 10
反射操作
**
__getattr__(self, name)
**:- 作用:当访问不存在的属性时被调用。可用于动态属性或提供默认值。
**
__setattr__(self, name, value)
**:- 作用:设置属性值时调用。
哈希与相等性
**
__hash__(self)
**:- 作用:定义对象的哈希值,使对象可用于集合、字典的键。
**
__eq__(self, other)
**:- 作用:定义相等性运算 (
==
)。
- 作用:定义相等性运算 (
**
__ne__(self, other)
**:- 作用:定义不等性运算 (
!=
)。
- 作用:定义不等性运算 (
辨析
__new__
和__init__
__new__
方法
作用:
__new__
是一个静态方法,用于创建对象。在对象创建时,它负责分配内存并返回新实例。它是在类被调用时最先执行的方法,在__init__
之前被调用。参数:
__new__(cls, ...)
,其中cls
是当前类的引用,后面的参数是传递给类构造函数的参数。返回值:
__new__
必须返回类的一个实例,通常通过super().__new__(cls)
来实现,表示用父类的__new__
方法创建对象。
适用场景:
- 当你需要控制对象的创建过程时,例如实现单例模式(确保某个类只创建一个实例)或子类化不可变类型(如
int
、str
、tuple
)时,可以使用__new__
。
示例:
1 |
|
输出:
1 |
|
在这个例子中,__new__
在对象创建时被调用,用于分配内存,而 __init__
随后负责初始化对象属性。
__init__
方法
作用:
__init__
是用于初始化对象的。它在对象已经被__new__
方法创建并分配内存之后调用,负责为对象设置初始状态(比如为实例变量赋值)。参数:
__init__(self, ...)
,其中self
是当前对象的实例,后面的参数是传递给类构造函数的参数。返回值:
__init__
没有返回值,因为它仅负责初始化对象。
示例:
1 |
|
输出:
1 |
|
这里 __init__
接受对象实例 self
作为第一个参数,用来设置对象的初始属性。
__new__
和 __init__
的关系
__new__
负责创建对象,在实例化过程中最先执行。__init__
负责初始化对象,在__new__
之后执行,设置对象的初始状态。
它们在对象实例化过程中的执行顺序如下:
- 调用
__new__
来创建对象,分配内存。 - 如果
__new__
返回的对象是当前类的实例,Python 会自动调用__init__
,对该对象进行初始化。
在子类中的使用
有时,在子类中需要重写 __new__
和 __init__
方法。例如,当我们继承不可变类型(如 int
或 tuple
)时,通常需要自定义 __new__
,因为这些类型的实例在创建之后无法修改。
示例:子类化不可变类型 int
1 |
|
输出:
1 |
|
在这个例子中,MyInt
继承了不可变类型 int
,需要在 __new__
中控制对象的创建,因为 int
的实例一旦创建就不能改变。而 __init__
仅用于添加额外的属性。
__new__
的典型使用场景:单例模式
__new__
常用于实现单例模式,即保证一个类只能创建一个实例。
示例:单例模式
1 |
|
在这个例子中,__new__
保证 Singleton
类只能有一个实例,即使多次调用类构造函数,返回的都是同一个实例。
__str_
_和__repr__
__str__
和 __repr__
是 Python 中两个非常重要的魔术方法,用于定义对象的字符串表示形式。虽然它们都负责返回对象的字符串表示,但它们有不同的使用场景和目的。
__str__(self)
- 作用:
__str__
定义了对象的用户友好的字符串表示。当你使用str()
函数或print()
打印对象时,调用的是__str__
方法。它的目的是返回一个易于阅读的字符串,供用户直接使用。 - 典型用途:输出对象的简明描述,主要供人类阅读。
__repr__(self)
作用:
__repr__
定义了对象的官方字符串表示,即面向开发者的表示形式。repr()
函数会调用__repr__
方法,通常期望返回一个尽可能精确且完整的字符串,能够明确地表示这个对象,并且在某些情况下能够用于重新创建对象(eval(repr(obj)) == obj
这种方式)。典型用途:调试和日志记录时,开发者用来查看对象的详细信息,便于追踪程序行为。
示例:
1 |
|
在这个例子中,__repr__
返回一个精确的、带有完整信息的字符串,这个字符串尽量能够帮助开发者了解对象的内部状态。
注意:如果一个类没有实现 __str__
,那么当你使用 print()
或 str()
函数时,Python 会退而调用 __repr__
。
如果一个类这两个方法都没有实现,那么当调用 print()
或 str()
时,Python 会使用该类的默认实现。这种默认行为由 object
类提供,返回一个包含类名和内存地址的字符串,表示对象的唯一标识符。
示例
1 |
|
解释
__main__
: 表示该类定义在当前模块(也就是主程序模块)中。如果类定义在另一个模块中,这部分会显示模块名。MyClass
: 是类的名称。0x7f83b2c4d880
: 是该对象在内存中的地址(这是十六进制的内存地址,表示对象的唯一位置)。
拓展
迭代器
在 Python 中,迭代是一种遍历容器(如列表、元组、字典、集合等)元素的方式。迭代器提供了一种访问容器元素的机制,避免显式使用索引。要理解迭代的工作原理,我们需要了解几个核心概念,包括迭代器(iterator)、可迭代对象(iterable)、__iter__
和 __next__
方法。
可迭代对象(Iterable)
- 定义:一个对象是可迭代的,如果它实现了
__iter__()
方法,返回一个迭代器,或者它定义了一个__getitem__()
方法(支持通过索引访问)。 - 示例:列表、元组、字符串、字典等都是可迭代对象。
可迭代对象可以使用 for
循环进行迭代,或者通过 iter()
函数显式地获得它的迭代器。
示例:
1 |
|
迭代器(Iterator)
- 定义:一个对象是迭代器,如果它实现了
__iter__()
方法并返回自身,以及实现了__next__()
方法。__next__()
方法返回下一个元素,当没有元素时抛出StopIteration
异常。 - 特点:迭代器是一次性可消耗的,即迭代器只能遍历一次,遍历完就无法重新开始。
示例:
1 |
|
__iter__
和 __next__
魔术方法
**
__iter__(self)
**:可迭代对象和迭代器都应该实现这个方法。对于可迭代对象,__iter__()
返回一个新的迭代器对象;对于迭代器,__iter__()
通常返回self
(即迭代器本身)。**
__next__(self)
**:这是迭代器对象实现的关键方法。它返回序列中的下一个元素,如果序列结束,则抛出StopIteration
异常。
自定义迭代器示例:
1 |
|
输出:
1 |
|
迭代的执行流程
- 当一个对象被用于
for
循环时,Python 会在后台调用该对象的__iter__()
方法,以获取一个迭代器。 - 然后,循环会反复调用迭代器的
__next__()
方法,直到抛出StopIteration
异常为止。
执行过程的分解:
1 |
|
生成器(Generators)
生成器是创建迭代器的一种简洁方式。它通过 yield
关键字逐步返回值,每次迭代时暂停函数的执行状态,保存上下文,以便下次继续。
- 定义生成器函数:生成器函数与普通函数的区别在于它使用了
yield
关键字,而不是return
。 - 生成器对象:调用生成器函数返回一个生成器对象,生成器对象是一个迭代器。
示例:
1 |
|
输出:
1 |
|
生成器的执行过程:
生成器函数在每次调用 next()
时会暂停在 yield
语句,保存当前的状态,并在下一次调用时从该状态继续执行。
1 |
|
iter()
函数与 next()
函数
- **
iter(object)
**:返回对象的迭代器。该函数首先检查对象是否实现了__iter__
方法,如果没有,则检查是否实现了__getitem__
,以支持通过索引访问。 - **
next(iterator, default)
**:调用迭代器的__next__()
方法返回下一个元素。如果迭代结束,并且提供了default
参数,则返回default
,否则抛出StopIteration
。
示例:
1 |
|
可迭代对象和迭代器的区别
- 可迭代对象:实现了
__iter__()
方法,可以返回一个迭代器,但它自身不一定是迭代器。 - 迭代器:实现了
__iter__()
和__next__()
方法,且__iter__()
通常返回自身,__next__()
负责返回序列中的下一个值。
可迭代对象和迭代器的主要区别在于,迭代器一次性消耗,即遍历完后不能再次迭代;而可迭代对象每次调用 __iter__
方法都会返回一个新的迭代器。
惰性迭代
迭代器和生成器的一个重要特性是惰性迭代,即它们不会一次性生成所有元素,而是按需逐个生成。当我们需要处理非常大的数据集时,这种特性非常有用。
示例:生成无限序列
1 |
|
生成器 count()
将一直生成数字,直到被手动终止。