定制类和魔法方法

在 Python 中,我们可以经常看到以双下划线 __ 包裹起来的方法,比如最常见的 __init__,这些方法被称为魔法方法(magic method)或特殊方法(special method)。简单地说,这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类,比如 __init__ 方法可以对实例属性进行初始化。

完整的特殊方法列表可在这里查看,本文介绍部分常用的特殊方法:

  • __new__

  • __str__ , __repr__

  • __iter__

  • __getitem__ , __setitem__ , __delitem__

  • __getattr__ , __setattr__ , __delattr__

  • __call__

new

在 Python 中,当我们创建一个类的实例时,类会先调用 __new__(cls[, ...]) 来创建实例,然后 __init__ 方法再对该实例(self)进行初始化。

关于 __new____init__ 有几点需要注意:

  • __new__ 是在 __init__ 之前被调用的;

  • __new__ 是类方法,__init__ 是实例方法;

  • 重载 __new__ 方法,需要返回类的实例;

一般情况下,我们不需要重载 __new__ 方法。但在某些情况下,我们想控制实例的创建过程,这时可以通过重载 __new_ 方法来实现。

让我们看一个例子:

在上面,我们定义了一个类 A,并重载了 __new__ 方法:当 keyA._dict 中时,直接返回 A._dict['key'],否则创建实例。

执行情况:

str & repr

先看一个简单的例子:

在上面,我们使用 print 打印一个实例对象,但如果我们想打印更多信息呢,比如把 name 也打印出来,这时,我们可以在类中加入 __str__ 方法,如下:

可以看到,使用 print 和 str 输出的是 __str__ 方法返回的内容,但如果直接显示则不是,那能不能修改它的输出呢?当然可以,我们只需在类中加入 __repr__ 方法,比如:

可以看到,现在直接使用 Foo('ethan') 也可以显示我们想要的结果了,然而,我们发现上面的代码中,__str____repr__ 方法的代码是一样的,能不能精简一点呢,当然可以,如下:

iter

在某些情况下,我们希望实例对象可被用于 for...in 循环,这时我们需要在类中定义 __iter__next(在 Python3 中是 __next__)方法,其中,__iter__ 返回一个迭代对象,next 返回容器的下一个元素,在没有后续元素时抛出 StopIteration 异常。

看一个斐波那契数列的例子:

getitem

有时,我们希望可以使用 obj[n] 这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 __getitem__ 方法,比如下面的例子:

我们还想更进一步,希望支持 obj[1:3] 这种切片方法来取值,这时 __getitem__ 方法传入的参数可能是一个整数,也可能是一个切片对象 slice,因此,我们需要对传入的参数进行判断,可以使用 isinstance进行判断,改后的代码如下:

现在,我们试试用切片方法:

上面,我们只是简单地演示了 getitem 的操作,但是它还很不完善,比如没有对负数处理,不支持带 step 参数的切片操作 obj[1:2:5] 等等,读者有兴趣的话可以自己实现看看。

__geitem__ 用于获取值,类似地,__setitem__ 用于设置值,__delitem__ 用于删除值,让我们看下面一个例子:

在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用:

getattr

当我们获取对象的某个属性,如果该属性不存在,会抛出 AttributeError 异常,比如:

那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入 __getattr__ 方法,比如:

现在,当我们调用不存在的属性(比如 z)时,解释器就会试图调用 __getattr__(self, 'z') 来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如 w ,会返回 None,因为 __getattr__ 默认返回就是 None,只有当 attr 等于 'z' 时才返回 0,如果我们想让 __getattr__ 只响应几个特定的属性,可以加入异常处理,修改 __getattr__ 方法,如下:

这里再强调一点,__getattr__ 只有在属性不存在的情况下才会被调用,对已存在的属性不会调用 __getattr__

__getattr__ 一起使用的还有 __setattr__, __delattr__,类似 obj.attr = value, del obj.attr,看下面一个例子:

call

我们一般使用 obj.method() 来调用对象的方法,那能不能直接在实例本身上调用呢?在 Python 中,只要我们在类中定义 __call__ 方法,就可以对实例进行调用,比如下面的例子:

使用如下:

可以看到,对实例进行调用就好像对函数调用一样。

小结

  • __new____init__ 之前被调用,用来创建实例。

  • __str__ 是用 print 和 str 显示的结果,__repr__ 是直接显示的结果。

  • __getitem__ 用类似 obj[key] 的方式对对象进行取值

  • __getattr__ 用于获取不存在的属性 obj.attr

  • __call__ 使得可以对实例进行调用

参考资料

Last updated

Was this helpful?