在 Python 中,我们可以经常看到以双下划线 __
包裹起来的方法,比如最常见的 __init__
,这些方法被称为魔法方法(magic method)或特殊方法(special method)。简单地说,这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类,比如 __init__
方法可以对实例属性进行初始化。
完整的特殊方法列表可在这里查看,本文就一些常见的方法简单做一个说明,方便记忆:
1、call
之前一直以Java作为第一工作语言,初始在接触Django源码时,经常会遇到一个问题,就是:卧槽,这怎么调过来的??习惯了Java语言Function to Next Funcation的分析步骤,突然间发现没有任何显式调用方法,这个Object怎么就执行了,这个时候就不得不提一下__call__
方法,本文就这个方法简单做一个说明,方便记忆~
python中所有的东西都被称为对象,对象分为可以被调用和不可以被调用,可调用对象:即是任何通过函数操作符()来调用的对象,例如函数,函数是对象,可以被调用:
def foo(x):
return x
if __name__ == '__main__':
a = foo(3)
print(a) # 3
print(callable(a)) # False
print(callable(foo)) # True
但是如果是类的话会发生什么情况呢?Python给类提供了名为__call__
的特别方法,该方法允许程序员创建可调用的对象(实例)。默认情况下,__call__
方法是没有实现的,这意味着大多数情况下实例是不可调用的。
class Test(object):
def method(self):
return 1
if __name__ == '__main__':
a = Test()
print(callable(a)) # False
如何解决这一个问题?这里就用到call函数
class Test(object):
def __call__(self):
return 1
if __name__ == '__main__':
a = Test()
print(callable(a)) # True
print(a()) # 1
任何设计都是为了解决问题而存在的,那么__call__
方法的用途是什么呢?__call__
在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法:
class Entity:
"""调用实体来改变实体的位置"""
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
"""改变实体的位置"""
self.x, self.y = x, y
if __name__ == '__main__':
e = Entity(1, 2, 3) # 创建实例
e(4, 5) # 实例可以象函数那样执行,并传入x y值,修改对象的x y
2、new & init
1)__new__
__new__
调用以创建一个 cls 类的新实例;如果
__new__
未返回一个 cls 的实例,则新实例的__init__()
方法就不会被执行;__new__
的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。
2)__init__
- 在实例通过
__new__
被创建之后,返回调用者之前调用; - 对象是由
__new__
和__init__
协作构造完成的
3、str & repr
class Foo(object):
def __init__(self, name):
self.name = name
>>> print Foo('ethan')
<__main__.Foo object at 0x10c37aa50>
在上面的例子中,我们使用 print 打印一个实例对象,但如果我们想打印更多信息呢,比如把 name 也打印出来,这时,我们可以在类中加入 __str__
方法,如下:
class Foo(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Foo object (name: %s)' % self.name
>>> print Foo('ethan') # 使用 print
Foo object (name: ethan)
>>>
>>> str(Foo('ethan')) # 使用 str
'Foo object (name: ethan)'
>>>
>>> Foo('ethan') # 直接显示
<__main__.Foo at 0x10c37a490>
可以看到,使用 print 和 str 输出的是 __str__
方法返回的内容,但如果直接显示则不是,那能不能修改它的输出呢?当然可以,我们只需在类中加入 __repr__
方法,比如:
class Foo(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Foo object (name: %s)' % self.name
def __repr__(self):
return 'Foo object (name: %s)' % self.name
>>> Foo('ethan')
'Foo object (name: ethan)'
可以看到,现在直接使用 Foo('ethan')
也可以显示我们想要的结果了,然而,我们发现上面的代码中,__str__
和 __repr__
方法的代码是一样的,能不能精简一点呢,当然可以,如下:
class Foo(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Foo object (name: %s)' % self.name
__repr__ = __str__
4、iter
在某些情况下,我们希望实例对象可被用于 for...in
循环,这时我们需要在类中定义 __iter__
和 next
(在 Python3 中是 __next__
)方法,其中,__iter__
返回一个迭代对象,next
返回容器的下一个元素,在没有后续元素时抛出 StopIteration
异常,看一个斐波那契数列的例子:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self): # 返回迭代器对象本身
return self
def next(self): # 返回容器下一个元素
self.a, self.b = self.b, self.a + self.b
return self.a
>>> fib = Fib()
>>> for i in fib:
... if i > 10:
... break
... print i
...
1
1
2
3
5
8
5、getitem
有时,我们希望可以使用 obj[n]
这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 __getitem__
方法,比如下面的例子:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in xrange(n):
a, b = b, a + b
return a
>>> fib = Fib()
>>> fib[0], fib[1], fib[2], fib[3], fib[4], fib[5]
(1, 1, 2, 3, 5, 8)
我们还想更进一步,希望支持 obj[1:3]
这种切片方法来取值,这时 __getitem__
方法传入的参数可能是一个整数,也可能是一个切片对象 slice,因此,我们需要对传入的参数进行判断,可以使用 isinstance
进行判断,改后的代码如下:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, slice): # 如果 n 是 slice 对象
a, b = 1, 1
start, stop = n.start, n.stop
L = []
for i in xrange(stop):
if i >= start:
L.append(a)
a, b = b, a + b
return L
if isinstance(n, int): # 如果 n 是 int 型
a, b = 1, 1
for i in xrange(n):
a, b = b, a + b
return a
现在,我们试试用切片方法:
>>> fib = Fib()
>>> fib[0:3]
[1, 1, 2]
>>> fib[2:6]
[2, 3, 5, 8]
上面,我们只是简单地演示了 getitem 的操作,但是它还很不完善,比如没有对负数处理,不支持带 step 参数的切片操作 obj[1:2:5]
等等,读者有兴趣的话可以自己实现看看。
__geitem__
用于获取值,类似地,__setitem__
用于设置值,__delitem__
用于删除值,让我们看下面一个例子:
class Point(object):
def __init__(self):
self.coordinate = {}
def __str__(self):
return "point(%s)" % self.coordinate
def __getitem__(self, key):
return self.coordinate.get(key)
def __setitem__(self, key, value):
self.coordinate[key] = value
def __delitem__(self, key):
del self.coordinate[key]
print 'delete %s' % key
def __len__(self):
return len(self.coordinate)
__repr__ = __str__
在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用:
>>> p = Point()
>>> p['x'] = 2 # 对应于 p.__setitem__('x', 2)
>>> p['y'] = 5 # 对应于 p.__setitem__('y', 5)
>>> p # 对应于 __repr__
point({'y': 5, 'x': 2})
>>> len(p) # 对应于 p.__len__
2
>>> p['x'] # 对应于 p.__getitem__('x')
2
>>> p['y'] # 对应于 p.__getitem__('y')
5
>>> del p['x'] # 对应于 p.__delitem__('x')
delete x
>>> p
point({'y': 5})
>>> len(p)
1
6、getattr
当我们获取对象的某个属性,如果该属性不存在,会抛出 AttributeError 异常,比如:
class Point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
>>> p = Point(3, 4)
>>> p.x, p.y
(3, 4)
>>> p.z
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-547-6dce4e43e15c> in <module>()
----> 1 p.z
AttributeError: 'Point' object has no attribute 'z'
那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入 __getattr__
方法,比如:
class Point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __getattr__(self, attr):
if attr == 'z':
return 0
>>> p = Point(3, 4)
>>> p.z
0
现在,当我们调用不存在的属性(比如 z)时,解释器就会试图调用 __getattr__(self, 'z')
来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如 w ,会返回 None,因为 __getattr__
默认返回就是 None,只有当 attr 等于 'z' 时才返回 0,如果我们想让 __getattr__
只响应几个特定的属性,可以加入异常处理,修改 __getattr__
方法,如下:
def __getattr__(self, attr):
if attr == 'z':
return 0
raise AttributeError("Point object has no attribute %s" % attr)
这里再强调一点,__getattr__
只有在属性不存在的情况下才会被调用,对已存在的属性不会调用 __getattr__
。
与 __getattr__
一起使用的还有 __setattr__
, __delattr__
,类似 obj.attr = value
, del obj.attr
,看下面一个例子:
class Point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __getattr__(self, attr):
if attr == 'z':
return 0
raise AttributeError("Point object has no attribute %s" % attr)
def __setattr__(self, *args, **kwargs):
print 'call func set attr (%s, %s)' % (args, kwargs)
return object.__setattr__(self, *args, **kwargs)
def __delattr__(self, *args, **kwargs):
print 'call func del attr (%s, %s)' % (args, kwargs)
return object.__delattr__(self, *args, **kwargs)
>>> p = Point(3, 4)
call func set attr (('x', 3), {})
call func set attr (('y', 4), {})
>>> p.z
0
>>> p.z = 7
call func set attr (('z', 7), {})
>>> p.z
7
>>> p.w
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __getattr__
AttributeError: Point object has no attribute w
>>> p.w = 8
call func set attr (('w', 8), {})
>>> p.w
8
>>> del p.w
call func del attr (('w',), {})
>>> p.__dict__
{'y': 4, 'x': 3, 'z': 7}