4.4.魔术方法
构造和初始化
每个人都知道一个最基本的魔术方法, __init__
。通过此方法我们可以定义一个对象的初始操作。但很多博客和个别书籍中都把__init__
当作类似于C++的构造方法,其实是错误的。__init__
并不是第一个被调用的方法。实际上,还有一个叫做 __new__
的方法,来构造这个实例。然后给在开始创建时候的初始化函数来传递参数。在对象生命周期的另一端,也有一个 __del__
方法。
__new__(cls, [...)
__new__
是在一个对象实例化的时候所调用的第一个方法。它的第一个参数是这个类,其他的参数是用来直接传递给 __init__
方法。 __new__
方法相当不常用,也不是很有用处,但是它有自己的特性,特别是当继承一个不可变的类型(比如一个tuple或者string):
class inch(float):
"Convert from inch to meter"
def __new__(cls, arg=0.0):
return float.__new__(cls, arg*0.0254)
print(inch(12))
0.30479999999999996
这个类可能不是很有用,但是它显示了如何扩展不可变类型的构造函数。如果我们尝试替代__init__
而不是__new__
,那么它将不起作用:
class inch(float):
"THIS DOESN'T WORK!!!"
def __init__(self, arg=0.0):
float.__init__(self, arg*0.0254)
try:
print(inch(12))
except TypeError as e:
print(e)
object.__init__() takes exactly one argument (the instance to
initialize)
重写初始值的版本不起作用,因为浮点类型的初始值是一个不可变对象。另一个__new__
的例子是单例模式。
class A:
pass
new1 = A()
new2 = A()
print(new1)
print(new2)
<__main__.A object at 0x7f307c57b700>
<__main__.A object at 0x7f307c57bc70>
class A(object):
_singleton = None
def __new__(cls, *args, **kwargs):
if not cls._singleton:
cls._singleton = object.__new__(cls, *args, **kwargs)
return cls._singleton
new1 = A()
new2 = A()
print(new1)
print(new2)
<__main__.A object at 0x7f307c5861f0>
<__main__.A object at 0x7f307c5861f0>
关于__new__
的一些规则:
__new__
是一个静态方法,但在重定义__new__
时,不需要加上@staticmethod
,因为它的名字已经暗示了这一点。__new__
的第一个参数必须是一个类;其余参数是构造函数调用所看到的参数。__new__
方法必须返回一个实例,如果返回None,则不会调用__init__
方法。
__init__(self, […)
此方法为类的初始化方法。当构造函数被调用的时候的任何参数都将会传给它。(比如如果我们调用 x = SomeClass(10, 'foo')
),那么 __init__
将会得到两个参数10和foo。 __init__
在Python的类定义中被广泛用到。
__del__(self)
如果 __new__
和 __init__
是对象的构造器的话,那么 __del__
就是析构器。它不实现语句 del x
(以上代码将不会翻译为 x.__del__()
)。它定义的是当一个对象进行垃圾回收时候的行为。当一个对象在删除的时需要更多的清洁工作的时候此方法会很有用,比如套接字对象或者是文件对象。注意,如果解释器退出的时候对象还存存在,就不能保证 __del__
能够被执行。
class A:
def __del__(self):
print("我被删除了")
a = A()
del a
我被删除了
用于比较的魔术方法
魔术方法 | 调用方式 | 描述 |
---|---|---|
__eq__(self, other) |
self == other | |
__nq__(self, other) |
self != other | |
__le__(self, other) |
self <= other | |
__ge__(self, other) |
self >= other | |
__lt__(self, other) |
self < other | |
__gt__(self, other) |
self > other |
class A:
def __init__(self, num=2):
self.num = num
def __repr__(self):
return str(self.num)
def __eq__(self, other):
return self.num == other
def __ne__(self, other):
return self.num != other
def __le__(self, other):
return self.num <= other
def __ge__(self, other):
return self.num >= other
def __lt__(self, other):
return self.num < other
def __gt__(self, other):
return self.num > other
a = A(5)
print(a == 5)
print(a != 5)
print(a > 4)
print(a < 6)
print(a >= 5)
print(a <= 6)
True
False
True
True
True
True
数值处理的魔术方法
一元运算符和内置函数
魔术方法 | 调用方式 | 描述 |
---|---|---|
__pos__(self) |
+self | |
__neg__(self) |
-self | |
__abs__(self) |
abs(self) | |
__invert__(self) |
~self |
class A:
def __init__(self, num=2):
self.num = num
def __repr__(self):
return str(self.num)
def __pos__(self):
new_num = 0 + self.num
return A(new_num)
def __neg__(self):
new_num = 0 - self.num
return A(new_num)
def __abs__(self):
new_num = abs(self.num)
return A(new_num)
a = A(-5)
print(+a)
print(-a)
print(abs(a))
-5
5
5
算术运算符
魔术方法 | 调用方式 | 描述 |
---|---|---|
__add__(self, other) |
self + other | |
__sub_(self, other) |
self - other | |
__mul__(self, other) |
self * other | |
__truediv__(self, other) |
self / other | |
__pow__(self, other) |
self ** other | |
__floordiv__(self, other) |
self // other | |
__mod__(self, other) |
self % other |
class A:
def __init__(self, num=2):
self.num = num
def __repr__(self):
return str(self.num)
def __add__(self, other):
new_num = self.num + other
return A(new_num)
def __sub__(self, other):
new_num = self.num - other
return A(new_num)
def __mul__(self, other):
new_num = self.num * other
return A(new_num)
def __truediv__(self, other):
new_num = self.num / other
return A(new_num)
def __pow__ (self, other):
new_num = self.num ** other
return A(new_num)
def __floordiv__(self, other):
new_num = self.num // other
return A(new_num)
def __mod__(self, other):
new_num = self.num % other
return A(new_num)
a = A(5)
print("a + 2 =", a + 2)
print("a - 2 =", a - 2)
print("a * 2 =", a * 2)
print("a / 2 =", a / 2)
print("a ** 2 =", a ** 2)
print("a // 2 =", a // 2)
print("a % 2 =", a % 2)
a + 2 = 7
a - 2 = 3
a * 2 = 10
a / 2 = 2.5
a ** 2 = 25
a // 2 = 2
a % 2 = 1
反运算
以下是一个普通的加法运算的例子:
# __add__
some_object + other
反运算是相同的,只是把操作数调换了位置:
# __radd__
other + some_object
所以,除了当与其他对象操作的时候自己会成为第二个操作数之外,所有的这些魔术方法都与普通的操作是相同的。大多数情况下,反运算的结果是与普通运算相同的。所以你可以你可以将 __radd__
与 __add__
等价。
魔术方法 | 调用方式 | 描述 |
---|---|---|
__radd__(self, other) |
other + self | |
__rsub_(self, other) |
other - self | |
__rmul__(self, other) |
other * self | |
__rtruediv__(self, other) |
other / self | |
__rpow__(self, other) |
other ** self | |
__rfloordiv__(self, other) |
other // self | |
__rmod__(self, other) |
other % self |
增强赋值
魔术方法 | 调用方式 | 描述 |
---|---|---|
__iadd__(self, other) |
self += other | |
__isub_(self, other) |
self -= other | |
__imul__(self, other) |
self *= other | |
__itruediv__(self, other) |
self /= other | |
__ipow__(self, other) |
self **= other | |
__ifloordiv__(self, other) |
self //= other | |
__imod__(self, other) |
self %= other |
表现你的类
如果有一个字符串来表示一个类将会非常有用,例如:
a = [1, 2, 3]
a
[1, 2, 3]
在Python中,有很多方法可以实现类定义内置的一些函数的返回值。
__str__(self)
定义当 str()
调用的时候的返回值。__repr__(self)
定义 repr()
被调用的时候的返回值。 str()
和 repr()
的主要区别在于 repr()
返回的是机器可读的输出,而 str()
返回的是人类可读的。
__unicode__(self)
定义当 unicode()
调用的时候的返回值。 unicode()
和 str()
很相似,但是返回的是unicode字符串。注意,如果对你的类调用 str()
然而你只定义了 __unicode__()
,那么将不会工作。你应该定义 __str__()
来确保调用时能返回正确的值。
__hash__(self)
定义当 hash()
调用的时候的返回值,它返回一个整数值,用来在字典中进行快速比较 __nonzero__(self)
定义当 bool()
调用的时候的返回值。本方法应该返回True或者False,取决于你想让它返回的值。
创建定制的序列
有很多方法让你的Python类行为可以像内置的序列(dict, tuple,list, string等等)。
现在我们开始讲如何在Python中创建定制的序列,这个时候该讲一讲协议。协议(Protocols)与其他语言中的接口很相似。它给你很多你必须定义的方法。然而在Python中的协议是很不正式的,不需要明确声明实现。事实上,他们更像一种指南。
我们为什么现在讨论协议?因为如果要定制容器类型的话需要用到这些协议:
- 实现不变容器的话有一个协议:实现不可变容器,你只能定义
__len__
和__getitem__
(一会会讲更多)。 - 可变容器协议则需要所有不可变容器的所有另外还需要
__setitem__
和__delitem__
。
最终,如果你希望你的对象是可迭代的话,你需要定义 __iter__
会返回一个迭代器。迭代器必须遵循迭代器协议,需要有 __iter__
(返回它本身) 和 next
。
容器的魔法
魔法方法 | 调用方法 | 描述 |
---|---|---|
__len__(self) |
len(self) | |
__index__(self) |
x[self] | 对象被作为索引使用的时候 |
__getitem__(self, key) |
self[key] | 使用索引访问元素时 |
__setitem__(self, key, val) |
self[key] = val | 对某个索引值赋值时 |
__delitem__(self, key) |
del self[key] | 删除某个索引值时 |
__iter__(self) |
for x in self | 迭代时 |
__contains__(self, value) |
value in self, value not in self | 使用 in 操作测试关系时 |
__concat__(self, value) |
self + other | 连接两个对象时 |
__reversed__(self) |
reversed(self) | 实现当 reversed() 被调用时的行为 |
可以调用的对象
在Python中,方法也是一种高等的对象。这意味着他们也可以被传递到方法中就像其他对象一样。这是一个非常惊人的特性。 在Python中,一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这是一个非常强大的特性让Python编程更加舒适甜美。 __call__(self, [args...])
允许一个类的实例像函数一样被调用。实质上说,这意味着 x()
与 x.__call__()
是相同的。注意 __call__
参数可变。这意味着你可以定义 __call__
为其他你想要的函数,无论有多少个参数。
__call__
在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了。
会话管理
在Python 2.5中,为了代码利用定义了一个新的关键词 with
语句。会话控制在Python中不罕见(之前是作为库的一部分被实现),直到 PEP343 被添加后。它被成为一级语言结构。你也许之前看到这样的语句:
with open('foo.txt') as bar:
# perform some action with bar
回话控制器通过包装一个 with
语句来设置和清理行为。回话控制器的行为通过两个魔术方法来定义:
__enter__(self)
定义当使用 with
语句的时候会话管理器应该初始块被创建的时候的行为。注意 __enter__
的返回值被 with
语句的目标或者 as
后的名字绑定。
__exit__(self, exception_type,exception_value, traceback)
定义当一个代码块被执行或者终止后会话管理器应该做什么。它可以被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作。如果代码块执行成功, exception_type
, exception_value
, 和 traceback
将会是 None
。否则的话你可以选择处理这个异常或者是直接交给用户处理。如果你想处理这个异常的话,确认 __exit__
在所有结束之后会返回 True
。如果你想让异常被会话管理器处理的话,那么就这样处理。
__enter
和 __exit__
对于明确有定义好的和日常行为的设置和清洁工作的类很有帮助。你也可以使用这些方法来创建一般的可以包装其他对象的会话管理器。以下是一个例子:
class Closer:
'''通过with语句和一个close方法来关闭一个对象的会话管理器'''
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj # bound to target
def __exit__(self, exception_type, exception_val, trace):
try:
self.obj.close()
except AttributeError: # obj isn't closable
print('Not closable.')
return True # exception handled successfully
with Closer(int(5)) as i:
i += 1
Not closable.