Python中的特殊方法
常见特殊方法
python里面最常见和常用的特殊方法莫过于 __init__() 了. 除了它之外还有一些相对常见的,见下表:
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| ① | 初始化一个实例对象 | x = MyClass() |
x.__init__() |
| ② | 字符串的官方呈现方式 |
repr(x) |
x.__repr__() |
| ③ | 转换为字符串 | str(x) |
x.__str__() |
| ④ | 转换为字节对象 | bytes(x) |
x.__bytes__() |
| ⑤ | 格式化字符串 | format(x, format_spec) |
x.__format__(format_spec) |
- 在实例对象创建之后会立马执行
__init__()方法。如果想控制实例的创建过程,那么需要使用__new__()方法。 - 依照惯例,
__repr__()应该返回一个合法的Python表达式字符串。 print(x)时会调用__str__()方法。- Python 3特有.
format_spec需要符合Format Specification Mini-Language语法规则。
迭代器行为特殊方法
| 备注 | 欲实现… | 具体实现 | Python内部调用… |
|---|---|---|---|
| ① | 迭代一个序列 | iter(seq) |
seq.__iter__() |
| ② | 回去迭代器的下一个值 | next(seq) |
seq.__next__() |
| ③ | 让迭代器逆序 | reversed(seq) |
seq.__reversed__() |
- 无论何时创建一个新的迭代器,
__iter__()方法都会被调用。 - 每获取一次迭代器的值,
__next__()会被执行一次。 __reversed__()方法不常见,它接收一个序列,然后返回一个迭代器,迭代器生成的元素与原序列的顺序是相反的.
计算属性
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| ① | 获取计算属性(无条件的) | x.my_property |
x.__getattribute__('my_property') |
| ② | 获取计算属性 (fallback) | x.my_property |
x.__getattr__('my_property') |
| ③ | 设置属性 | x.my_property = value |
x.__setattr__('my_property', value) |
| ④ | 删除属性 | del x.my_property |
x.__delattr__('my_property') |
| ⑤ | 列出所有属性和方法 | dir(x) |
x.__dir__() |
如果定义了
__getattr__()或__getattribute__()方法,则__dir__()方法是有用的。通常,dir(x)只会列出常规的属性和方法。如果__getattr__()方法动态处理颜色属性,dir(x)不会将颜色列为可用属性之一。覆盖__dir__()方法允许你将颜色作为一个可用属性列出,这对那些希望使用你的类而不深入其内部的人很有帮助。
__getattr__() 和__getattribute__() 方法之间的区别很微妙但很重要。用两个例子来解释:
class Dynamo:
def __getattr__(self, key):
if key == 'color': ①
return 'PapayaWhip'
else:
raise AttributeError ②
>>> dyn = Dynamo()
>>> dyn.color ③
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color ④
'LemonChiffon'
| ① | 属性名作为字符串传递到__getattr__() 方法中。如果名称是color,该方法返回一个值。(在本例中,它只是一个硬编码的字符串,但通常会执行某种计算并返回结果。) |
|---|---|
| ② | 如果属性名未定义,__getattr__()方法需要引发AttributeError异常,否则代码将在访问未定义的属性时静默失败。(从技术上讲,如果该方法没有引发异常或显式返回值,它将返回None,即Python的空值。这意味着没有显式定义的all属性将是None。) |
| ③ | dyn实例没有一个名为color的属性,因此调用__getattr__()方法来提供一个计算值。 |
| ④ | 显式设置dyn.color后,__getattr__()方法将不再被调用来为dyn.color提供一个值,因为dyn.color已经在实例中定义了。 |
另一方面,__getattribute__()方法是绝对和无条件的,也就是属性值写死了,无法更改。
class SuperDynamo:
def __getattribute__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
>>> dyn = SuperDynamo()
>>> dyn.color ①
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color ②
'PapayaWhip'
| ① | 调用__getattribute__()方法来为dyn.color提供一个值。 |
|---|---|
| ② | 即使显式地设置了dyn.color, __getattribute__()方法仍然会被调用来为dyn.color提供一个值。如果存在,__getattribute__()方法会被无条件地调用,用于每个属性和方法查找,即使是在创建实例后显式设置的属性。 |
☞如果你的类定义了一个
__getattribute__()方法,你可能还想定义一个__setattr__()方法,并在它们之间协调以跟踪属性值。否则,在创建实例之后,设置的任何属性都将消失在虚空中。
需要格外小心__getattribute__() 方法,因为当Python在你的类上查找方法名时,它也会被调用。
class Rastan:
def __getattribute__(self, key):
raise AttributeError ①
def swim(self):
pass
>>> hero = Rastan()
>>> hero.swim() ②
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattribute__
AttributeError
| ① | 这个类定义了一个__getattribute__()方法,该方法总是引发AttributeError 异常。任何属性或方法查找都不会成功。 |
|---|---|
| ② | 当你调用hero.swim()时,Python会在Rastan类中查找swim()方法。此查找通过__getattribute__()方法,因为所有属性和方法查找都通过__getattribute__() 方法。在本例中,__getattribute__() 方法引发AttributeError异常,因此方法查找失败,因此方法调用失败。 |
函数行为特殊方法
通过定义__call__() 方法,可以使一个类的实例成为可调用的——就像一个函数是可调用的一样。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 像调用函数一样调用实例化对象 | my_instance() |
my_instance.__call__() |
zipfile模块使用这个来定义一个类,该类可以用给定的密码解密加密的zip文件。zip解密算法要求在解密过程中存储状态。将解密器定义为类允许在解密器类的单个实例中维护这种状态。
状态在__init__()方法中初始化,并在文件解密时更新。但由于类也是可调用的像一个函数,你可以传递实例作为map()函数的第一个参数,像这样:
# excerpt from zipfile.py
class _ZipDecrypter:
.
.
.
def __init__(self, pwd):
self.key0 = 305419896 ①
self.key1 = 591751049
self.key2 = 878082192
for p in pwd:
self._UpdateKeys(p)
def __call__(self, c): ②
assert isinstance(c, int)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
self._UpdateKeys(c)
return c
.
.
.
zd = _ZipDecrypter(pwd) ③
bytes = zef_file.read(12)
h = list(map(zd, bytes[0:12])) ④
| ① | _ZipDecryptor类以三个旋转密钥的形式维护状态,稍后在_UpdateKeys()方法中更新这些密钥(这里没有显示)。 |
|---|---|
| ② | 这个类定义了一个__call__()方法,使类实例像函数一样可调用。在本例中,__call__() 方法解密zip文件的单个字节,然后根据被解密的字节更新旋转密钥。 |
| ③ | zd是_ZipDecryptor 类的一个实例。pwd变量被传递给__init__()方法,在那里它被存储并用于第一次更新旋转键。 |
| ④ | 给定zip文件的前12个字节,通过将字节映射到zd来解密它们,实际上调用zd 12次,这将调用__call__() 12次,这将更新其内部状态并返回结果字节12次。 |
集合行为特殊方法
如果类充当一组值的容器——也就是说,查询类是否“包含”一个值——那么它可能应该定义以下特殊方法。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 获取元素数量 | len(s) |
s.__len__() |
|
| 是否包含特殊指定值 | x in s |
s.__contains__(x) |
cgi 模块在其FieldStorage 类中使用这些方法,该类表示提交到动态web页面的所有表单字段或查询参数。
# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs: ①
do_search()
# An excerpt from cgi.py that explains how that works
class FieldStorage:
.
.
.
def __contains__(self, key): ②
if self.list is None:
raise TypeError('not indexable')
return any(item.name == key for item in self.list) ③
def __len__(self): ④
return len(self.keys()) ⑤
| ① | 一旦你创建了一个cgi.FieldStorage类,就可以使用in操作符来检查查询字符串中是否包含特定参数。 |
|---|---|
| ② | __contains__()方法是实现此功能的魔法。当你说if 'q' in fs时,Python会在fs对象上查找__contains__()方法,该方法在cgi.py中定义。值 'q'作为key参数传递给__contains__()方法。 |
| ③ | any() 函数接受一个generator表达式,如果生成器输出任何项,则返回True 。any() 函数足够智能,只要找到第一个匹配就会停止。 |
| ④ | 同样的FieldStorage 类也支持返回它的长度,所以可以使用len(fs),它会调用FieldStorage类上的__len__() 方法来返回它标识的查询参数的数量。 |
| ⑤ | self.keys()方法检self.list查是否为None,因此__len__方法不需要重复这个错误检查。 |
字典行为特殊方法
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 通过键获取值 | x[key] |
x.__getitem__(key) |
|
| 通过键设置值 | x[key] = value |
x.__setitem__(key, value) |
|
| 删除键-值对 | del x[key] |
x.__delitem__(key) |
|
| 为缺失键设置默认值 | x[nonexistent_key] |
x.__missing__(nonexistent_key) |
cgi模块中的' FieldStorage '类也定义了这些特殊的方法,这意味着你可以这样做:
# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
do_search(fs['q']) ①
# An excerpt from cgi.py that shows how it works
class FieldStorage:
.
.
.
def __getitem__(self, key): ②
if self.list is None:
raise TypeError('not indexable')
found = []
for item in self.list:
if item.name == key: found.append(item)
if not found:
raise KeyError(key)
if len(found) == 1:
return found[0]
else:
return found
| ① | fs对象是cgi.FieldStorage的一个实例。但是仍然可以计算像fs['q'] 这样的表达式。 |
|---|---|
| ② | fs['q'] 调用__getitem__()方法,键参数设置为'q' 。然后在其内部维护的查询参数列表(self.list)中查找.name与给定键匹配的项。 |
数行为特殊方法
使用适当的特殊方法,可以定义自己的类,使它们的行为类似于数字。也就是说,可以对它们进行加、减和其他数学运算。这就是分数是如何实现的- Fraction 类实现了这些特殊的方法,然后你可以这样做:
from fractions import Fraction
>>> x = Fraction(1, 3) # 分子为1, 分母为3
>>> x / 3
Fraction(1, 9)
以下是实现数字类所需的特殊方法的完整列表。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 加 | x + y |
x.__add__(y) |
|
| 减 | x - y |
x.__sub__(y) |
|
| 乘 | x * y |
x.__mul__(y) |
|
| 除 | x / y |
x.__truediv__(y) |
|
| 向下取整 | x // y |
x.__floordiv__(y) |
|
| 求模 | x % y |
x.__mod__(y) |
|
| 向下取整 & 求模 | divmod(x, y) |
x.__divmod__(y) |
|
| 乘方 | x ** y |
x.__pow__(y) |
|
| 左位移 | x << y |
x.__lshift__(y) |
|
| 右位移 | x >> y |
x.__rshift__(y) |
|
位与and |
x & y |
x.__and__(y) |
|
位异或 xor |
x ^ y |
x.__xor__(y) |
|
位或or |
`x | y` |
如果x是实现这些方法的类的实例,那就很好了。但如果它没有实现其中的一个呢?或者更糟,如果它实现了它,但不能处理某些类型的参数怎么办?例如:
from fractions import Fraction
>>> x = Fraction(1, 3)
>>> 1 / x
Fraction(3, 1)
这并非取一个分数除以一个整数的情况(如前面的例子)。这种情况很简单:x / 3调用x.__truediv__(3),而Fraction 类的__truediv__() 方法处理所有的数学运算。但是整数不知道如何用分数做算术运算。那么为什么这个例子行得通呢?
第二组算术特殊方法具有反射操作数。给定一个需要两个操作数的算术运算(例如:x / y),有两种方法:
- 让
x除以y, 或者 - 让
y分解成x
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 加 | x + y |
y.__radd__(x) |
|
| 减 | x - y |
y.__rsub__(x) |
|
| 乘 | x * y |
y.__rmul__(x) |
|
| 除 | x / y |
y.__rtruediv__(x) |
|
| 向下取整 | x // y |
y.__rfloordiv__(x) |
|
| 取模 | x % y |
y.__rmod__(x) |
|
| 向下取整 & 取模 | divmod(x, y) |
y.__rdivmod__(x) |
|
| 乘方 | x ** y |
y.__rpow__(x) |
|
| 左位移 | x << y |
y.__rlshift__(x) |
|
| 右位移 | x >> y |
y.__rrshift__(x) |
|
按位 and |
x & y |
y.__rand__(x) |
|
按位xor |
x ^ y |
y.__rxor__(x) |
|
按位or |
`x | y` |
但是等等! 还有更多!如果正在执行原位操作,比如x /= 3,那么甚至可以定义更多特殊的方法。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 原位 加 | x += y |
x.__iadd__(y) |
|
| 原位减 | x -= y |
x.__isub__(y) |
|
| 原位 乘 | x *= y |
x.__imul__(y) |
|
| 原位 除 | x /= y |
x.__itruediv__(y) |
|
| 原位向下取整 | x //= y |
x.__ifloordiv__(y) |
|
| 原位取模 | x %= y |
x.__imod__(y) |
|
| 原位乘方 | x **= y |
x.__ipow__(y) |
|
| 原位位左移 | x <<= y |
x.__ilshift__(y) |
|
| 原位位右移 | x >>= y |
x.__irshift__(y) |
|
原位按位 and |
x &= y |
x.__iand__(y) |
|
原位按位 xor |
x ^= y |
x.__ixor__(y) |
|
原位按位 or |
`x | = y` |
如果想对原位操作数做一些特殊的优化,只需要定义原位方法,比如__itruediv__()方法。否则,Python将从本质上重新定义原位操作数,使用常规操作数+变量赋值。
还有一些一元数学运算,可以作用在类数对象自己。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 取负 | -x |
x.__neg__() |
|
| 取正 | +x |
x.__pos__() |
|
| 绝对值 | abs(x) |
x.__abs__() |
|
| 逆/反 | ~x |
x.__invert__() |
|
| 复数 | complex(x) |
x.__complex__() |
|
| 整数 | int(x) |
x.__int__() |
|
| 浮点数 | float(x) |
x.__float__() |
|
| 四舍五入 | round(x) |
x.__round__() |
|
| 四舍五入并设置小数点 | round(x, n) |
x.__round__(n) |
|
最小取整 >= x |
math.ceil(x) |
x.__ceil__() |
|
最大取整 <= x |
math.floor(x) |
x.__floor__() |
|
将x截断为接近0的整数 |
math.trunc(x) |
x.__trunc__() |
|
| PEP 357 | 数字作为列表索引 | a_list[x] |
a_list[x.__index__()] |
可比较行为特殊方法
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 相等 | x == y |
x.__eq__(y) |
|
| 不相等 | x != y |
x.__ne__(y) |
|
| 小于 | x < y |
x.__lt__(y) |
|
| 不大于 | x <= y |
x.__le__(y) |
|
| 大于 | x > y |
x.__gt__(y) |
|
| 不小于 | x >= y |
x.__ge__(y) |
|
| 布尔上下文中的真值 | if x: |
x.__bool__() |
☞如果只定义
__lt__()方法但没有__gt__()方法,Python将使用__lt__()方法,并交换操作数。然而,Python不会组合方法。例如,如果你定义了一个__lt__()方法和一个__eq__()方法并尝试测试是否x <= y, Python并不会依次调用__lt__()和__eq__()。它只会调用__le__()方法。😺
序列化行为特殊方法
Python支持序列化和反序列化任意对象。(大多数Python教程将此过程称为pickling和unpickling。)这对于将状态保存到文件并在稍后恢复文件非常有用。所有的原生数据类型都已经支持pickle。如果创建了一个希望能够pickle的自定义类,那么请阅读pickle协议,了解何时以及如何调用以下特殊方法。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 自定义对象复制 | copy.copy(x) |
x.__copy__() |
|
| 深度复制 | copy.deepcopy(x) |
x.__deepcopy__() |
|
| * | pickling之前获取对象状态 |
pickle.dump(x, file) |
x.__getstate__() |
| * | 序列化对象 | pickle.dump(x, file) |
x.__reduce__() |
| * | 序列化对象 | pickle.dump(x, file, protocol_version) |
x.__reduce_ex__(protocol_version) |
| * | 加载序列化对象 | x = pickle.load(file) |
x.__getnewargs__() |
| * | unpickling之后恢复对象的状态 |
x = pickle.load(file) |
x.__setstate__() |
* 要重新创建一个序列化对象,Python需要创建一个看起来像序列化对象的新对象,然后在新对象上设置所有属性的值。
__getnewargs__()方法控制对象的创建方式,然后__setstate__()方法控制属性值的恢复方式。
with 语句块行为特殊方法
一个with 块定义了一个运行时上下文;当执行with 语句时,进入上下文,在执行块中的最后一条语句后,退出上下文。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
进入with 块语句时执行一些代码 |
with x: |
x.__enter__() |
|
离开 with 块语句是执行一些代码 |
with x: |
x.__exit__(exc_type, exc_value, traceback) |
举例:
# excerpt from io.py:
def _checkClosed(self, msg=None):
'''Internal: raise an ValueError if file is closed
'''
if self.closed:
raise ValueError('I/O operation on closed file.'
if msg is None else msg)
def __enter__(self):
'''Context management protocol. Returns self.'''
self._checkClosed() ①
return self ②
def __exit__(self, *args):
'''Context management protocol. Calls close()'''
self.close() ③
| ① | file对象同时定义了__enter__() 和__exit__() 方法。__enter__()方法检查文件是否已打开;如果不是,_checkClosed()方法将引发异常。 |
|---|---|
| ② | __enter__()方法应该几乎总是返回self -这是with块将用来分派属性和方法的对象。 |
| ③ | 在with块之后,file对象自动关闭。怎么做到的?在__exit__() 方法中,它调用self.close()。 |
☞
__exit__()方法将始终被调用,即使在with块内引发异常。事实上,如果异常被引发,异常信息将被传递给'__exit__()方法。
更多关于上下文管理的详情, 见 Closing Files Automatically 和 Redirecting Standard Output.
一些深奥的东西
如果你够牛,那么就可以完全控制类的实现,比如定义属性,决定哪些类属于你的子类等等。
| 备注 | 欲实现… | 具体实现… | Python内部调用… |
|---|---|---|---|
| 类构造器 | x = MyClass() |
x.__new__() |
|
| * | 销毁类 | del x |
x.__del__() |
| 定义一组特定属性 | x.__slots__() |
||
| 自定义hash 值 | hash(x) |
x.__hash__() |
|
| 获取属性值 | x.color |
type(x).__dict__['color'].__get__(x, type(x)) |
|
| 设置属性值 | x.color = 'PapayaWhip' |
type(x).__dict__['color'].__set__(x, 'PapayaWhip') |
|
| 删除属性 | del x.color |
type(x).__dict__['color'].__del__(x) |
|
控制一个对象是否为你的类的实例 |
isinstance(x, MyClass) |
MyClass.__instancecheck__(x) |
|
控制一个类是否是你的类的子类 |
issubclass(C, MyClass) |
MyClass.__subclasscheck__(C) |
|
控制一个类是否为抽象基类的子类 |
issubclass(C, MyABC) |
MyABC.__subclasshook__(C) |
* Python调用
__del__()特殊方法是很复杂的,设计到Python的内存回收机制等,我也不是很懂,就不多说了。

浙公网安备 33010602011771号