最近在看一些项目源码,对python的魔术方法一直有点迷糊,所以花了些时间,认真研究了一下,总结在这里。
魔术方法
在python中,经常看到一个用__
包裹起来的方法,这些方法被称为魔术方法或特殊方法。最常见如__init__
,对实例属性进行初始化。
new
在 Python 中,当我们创建一个类的实例时,类会先调用 __new__(cls[, ...])
来创建实例,然后 __init__
方法再对该实例(self)进行初始化。
关于 new 和 init 有几点需要注意:
- new 是在 init 之前被调用的;
- new 是类方法,init 是实例方法;
- 重载 new 方法,需要返回类的实例;
定义一个类A,重载new方法:
class A(object):
_dict = dict()
def __new__(cls):
if 'key' in A._dict:
print("EXISTS")
return A._dict['key']
else:
print("NEW")
return object.__new__(cls)
def __init__(self):
print("INIT")
A._dict['key'] = self
# 创建一个实例a1
> a1 = A()
NEW
INIT
# 创建一个实例a2失败,直接返回a1
> a2 = A()
EXISTS
INIT
# 清空dict
> A._dict.clear()
# 创建一个实例a3成功
> a3 = A()
NEW
INIT
# 查看dict内容
> A._dict
{'key': <__main__.A at 0x10834ab70>}
str & repr
这两个魔法方法一般会放到一起进行讲解,它们的主要差别为:
__str__
强调可读性,而__repr__
强调准确性/标准性__str__
的目标人群是用户,而__repr__
的目标人群是机器,它的结果是可以被执行的- %s调用
__str__
方法,而%r调用__repr__
方法
# 第一版
class Foo(object):
def __init__(self, name):
self.name = name
> Foo('boheyan')
<__main__.Foo at 0x10834c160>
# 第二版
class Foo(object):
def __init__(self, name):
self.name = name
# 创建一个__str__方法
def __str__(self):
return 'Foo object (name: %s)' % self.name
> Foo('boheyan')
<__main__.Foo at 0x108341fd0>
> str(Foo('boheyan'))
Foo object (name: boheyan)
# 第三版
class Foo(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Foo object (name: %s)' % self.name
# 创建一个__repr__方法,为了简单,直接赋值
__repr__ = __str__
> Foo('boheyan')
Foo object (name: boheyan)
> str(Foo('boheyan'))
Foo object (name: boheyan)
这里值得注意的是,如果只定义了__str__或__repr__其中一个,那会是什么结果?
- 如果只定义了
__str__
,那么repr(person)输出<__main__.Person object at 0x1093642d0>
- 如果只定义了
__repr__
,那么str(person)与repr(person)结果是相同的 也就是说,__repr__
在表示类时,是一级的,如果只定义它,那么__str__ = __repr__
。
iter
在某些情况下,我们希望实例对象可被用于 for…in 循环,这时我们需要在类中定义 __iter__
和 __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, end=' ')
> 1 1 2 3 5 8
getitem
有时,我们希望可以使用 obj[n] 这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 __getitem__
方法,比如下面的例子:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(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)
如果想进一步实现切片功能:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, slice): # 如果 n 是 slice 对象
a, b = 1, 1
start, stop = n.start, n.stop
step = n.step
L = []
for i in range(stop):
if i >= start:
L.append(a)
a, b = b, a + b
if step: # 如果有step,进一步处理
lst = []
n = len(L) // step
for i in range(n+1):
lst.append(L[i*step])
return lst
return L # 没有step,直接返回L
if isinstance(n, int): # 如果 n 是 int 型
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
> fib = Fib()
> fib[3:10]
[3, 5, 8, 13, 21, 34, 55]
> fib[3:10:3]
[3, 13, 55]
__geitem__
用于获取值,类似地,__setitem__
用于设置值,__delitem__
用于删除值
getattr
__getattr__
在属性不存在的情况下会被调用,对已存在的属性不会调用 __getattr__
__setattr__
用于设置属性, __delattr__
用于删除属性
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), {})
> print(p.x, p.y, p.z)
3 4 0
> p.w = 5
call func set attr (('w', 5), {})
> print(p.w)
5
> del p.w
call func del attr (('w',), {})
call
我们一般使用 obj.method() 来调用对象的方法,那能不能直接在实例本身上调用呢?在 Python 中,只要我们在类中定义 call 方法,就可以对实例进行调用,比如下面的例子:
class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def __call__(self, z):
return self.x + self.y + z
> p = Point(3, 4)
> p(5) # # 传入参数,对实例进行调用,对应 p.__call__(5)
12
统计
魔法方法 | 什么时候被调用 | 解释 |
---|---|---|
new(cls [,…]) | instance = MyClass(arg1, arg2) | __new__在实例创建时调用 |
init(self [,…]) | instance = MyClass(arg1,arg2) | __init__在实例创建时调用 |
cmp(self) | self == other, self > other 等 | 进行比较时调用 |
pos(self) | +self | 一元加法符号 |
neg(self) | -self | 一元减法符号 |
invert(self) | ~self | 按位取反 |
index(self) | x[self] | 当对象用于索引时 |
nonzero(self) | bool(self) | 对象的布尔值 |
getattr(self, name) | self.name #name不存在 | 访问不存在的属性 |
setattr(self, name) | self.name = val | 给属性赋值 |
_delattr(self, name) | del self.name | 删除属性 |
getattribute(self,name) | self.name | 访问任意属性 |
getitem(self, key) | self[key] | 使用索引访问某个元素 |
setitem(self, key) | 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进行成员测试 |
call(self [,…]) | self(args) | “调用”一个实例 |
enter(self) | with self as x: | with声明的上下文管理器 |
exit(self, exc, val, trace) | with self as x: | with声明的上下文管理器 |
getstate(self) | pickle.dump(pkl_file, self) | Pickling |
setstate(self) | data = pickle.load(pkl_file) | Pickling |
参考
http://funhacks.net/explore-python/Class/w.html http://kaito-kidd.com/2017/02/22/python-magic-methods/ http://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html