常见特殊方法
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 "", line 1, in
File "", 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.__or__(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
y.__ror__(x)
但是等等! 还有更多!如果正在执行原位操作,比如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
x.__ior__(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__()方法。
一些深奥的东西
如果你够牛,那么就可以完全控制类的实现,比如定义属性,决定哪些类属于你的子类等等。
备注
欲实现…
具体实现…
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的内存回收机制等,我也不是很懂,就不多说了。