跳转至

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中的协议是很不正式的,不需要明确声明实现。事实上,他们更像一种指南。

我们为什么现在讨论协议?因为如果要定制容器类型的话需要用到这些协议:

  1. 实现不变容器的话有一个协议:实现不可变容器,你只能定义 __len____getitem__ (一会会讲更多)。
  2. 可变容器协议则需要所有不可变容器的所有另外还需要 __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.

参考

  1. 文章