python魔法方法
# python魔法方法
# 简介
魔法方式(Magic methods)是python的内置函数,一般以双下划线开头和结尾。
# 链接资料
segmentfault
# 1. 什么是魔法方法
魔法方式(Magic methods)是python的内置函数,一般以双下划线开头和结尾,比如__add__,__new__等。每个魔法方法都有对应的一个内置函数或者运算符。当我们对象使用这些方法时,相当于对这个对象的这类方法进行重写(如运算符重载)。魔法方法的存在是对类或函数进行了提炼,供python解释器直接调用。当使用len(obj)
时,实际上调用的就是obj.__len__
方法,其它同理。
dir()可以查看对象的所有方法和属性,其中双下划线开头和结尾的就是该对象具有的魔法方法。以整数对象为例:
>>>dir(int)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
2
可以看到,整数对象下具有__add__
方法,这也是为什么我们可以直接在python中运算1+2
,当python识别到+
时,就去调用该对象的__add__
方法来完成计算。比如我们想给自己的对象定义+方法:
class A:
def __init__(self, a):
self.a = a
def __add__(self, others):
# return self.a+others.a
return [self.a, others.a]
a = A(3)
b = A(3)
c = a + b # 自定义的__add__方法,实现是将两个对象的a属性合并到一个列表中
print(c)
print(a.__add__(b)) # 等价于直接调用
2
3
4
5
6
7
8
9
10
11
12
13
14
同理,再举一个__len__
魔法方法的例子来帮助理解。我们定义一个list对象l,通过dir(l)可以看到该对象中具有__len__
方法,即表明在python中可以通过len(l)来返回其列表长度。我们在自己定义的对象中当然也可以自定第该方法,来实现我们想通过len()返回的结果。
l=[1,2,3]
dir(l)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
print(len(l)) # 3
2
3
4
5
自定义一个对象:
class B:
def __init__(self,a):
self.a=a
>>>b=B(1)
>>>len(b)#因为对象中未定义__len__,因此调用len(b)会报错,没有该方法
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: object of type 'B' has no len()
#添加__len__方法
class B:
def __init__(self,a):
self.a=a
def __len__(self):
print('this is magic method len')
return 2
>>>a=B(1)
>>>print(len(a))
this is magic method len
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以看到,魔术方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用这些「魔法方法」,我们可以非常方便地给类添加特殊的功能。
# 2. 常用的魔法方法
魔法方法大致分为如下几类:
- 构造与初始化
- 类的表示
- 访问控制
- 比较、运算等操作
- 容器类操作
- 可调用对象
- 序列化
# 2.1 构造与初始化
对类的初始化一般会涉及三个魔法方法,__init__
,__new__
和__del__
;
# **__init__**
使用场景:定义类时
在创建类时,我们可以手动添加一个 init() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
init_() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。
class TheFirstDemo:
# 构造方法(创建实例的时候会先执行__new__方法,然后在执行__init__方法)
def __init__(self):
print("调用构造方法")
TheFirstDemo()
"""
调用构造方法
"""
2
3
4
5
6
7
8
9
10
# __new__
使用场景:__new__
的使用场景如单例模式、工厂模式,以及一些不可变对象的继承上。这类应用非常值得关注并使用,可以大大的让代码看起来优美和简洁
当我们初始化一个类是,a=A(1),首先调用的并不是__init__
函数,而是__new__
;初始化一个类有两步,分别为:
- 调用该类的
__new__
方法,并返回该类的实例对象; - 调用该类的
__init__
方法,对实例对象进行初始化。
其中__new__
用法如下:
(1) __new__(cls,*args,**kwargs)
:至少要有一个参数cls
,代表传入的类,此参数在实例化时由 Python 解释器自动提供,若返回该类的对象实例(self),后面的参数直接传递给__init__
。
(2) __new__
可以决定是否使用__init__
方法,但是,执行了__new__
,并不一定会进__init__
,只有__new__
返回了,当前类cls
的实例,当前类的__init__
才会进入。即使返回父类的实例也不行,必须是当前类的实例;
(3) object将__new__()
方法定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类,此参数在实例化时由Python解释器自动提供。
(4) __init__()
有一个参数self,该self参数就是__new__()
返回的实例
class A:
def __init__(self,a,b):
print('this is A init')
print(self)
self.a=a
self.b=b
def __new__(cls, *args, **kwargs):
print('this is A new')
print('args:%s'%args)
print('kwargs:%s'%kwargs)
print(cls)
print(object.__new__(cls))#<__main__.A object at 0x000001BCD98FB3D0>,一个A的对象实例
return object.__new__(cls)#创建实例并返回
>>>a=A(1,b=10)
this is A new # 先进入__new__
args:1
kwargs:{'b': 10}
<class '__main__.A'>
<__main__.A object at 0x000001BCD98FB3D0>
this is A init # 再进入__init__
<__main__.A object at 0x000001D0BC3EB3D0>#self就是__new__返回的对象实例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
其中object是所有类的基类object.__new__
则返回当前传入类cls的实例对象;当前类的__new__
返回当前类的实例对象后,再进入__init__
对实例进行初始化。
如果 __new__
未返回当前类的实例,则不会进入当前类的__init__
class A:
def __init__(self,a,b):
print('this is A init')
self.a=a
self.b=b
def __new__(cls, *args, **kwargs):
print('this is A new')
print('args:%s'%args)
print('kwargs:%s'%kwargs)
print(cls)
>>>m=A(1,b=10)
this is A new
args:1
kwargs:{'b': 10}
<class '__main__.A'>
>>>print(m.a)#报错,未进入到当前类的__init__进行初始化
AttributeError: 'NoneType' object has no attribute 'a'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
进一步,__new__
返回的是其它实例,当前实例的__init__
也不会调用
class A(object):
def __init__(self,a):
print('this is A init')
self.a=a
def __new__(cls, *args, **kwargs):
print('this is A new')
print(cls)
print(object.__new__(cls))
return object.__new__(cls)
class B(A):
def __init__(self, a):
super().__init__(a)
print('this is B init')
def __new__(cls ,*args, **kwargs):
print('this is B new')
print(cls)
return super().__new__(A, *args, **kwargs)#这里返回的是A的实例对象<__main__.A object at 0x000002A08683B370>,因此不会调用B的__init__;
>>>m=B(1)#初始化过程中,调用的顺序为:子类B的__new__》父类A的__new__,调用Object.__new__返回A的实例对象》返回到子类B的__new__中,返回A对象的实例,不是B的对象实例,因此不再调用B的__init__
this is B new
<class '__main__.B'>
this is A new
<class '__main__.A'>
<__main__.A object at 0x000002A08683B370>
#修改子类的__new__:
class B(A):
def __init__(self, a):
super().__init__(a)
print('this is B init')
def __new__(cls ,*args, **kwargs): # cls默认代表传入
print('this is B new')
print(cls)
return super().__new__(cls, *args, **kwargs) # 即是通过super()调用父类object的初始__new__方法来创建实例。返回B的对象实例
>>>m=B(1)
this is B new
<class '__main__.B'>
this is A new
<class '__main__.B'>
<__main__.B object at 0x00000167C541B370>
this is A init
this is B init
#调用顺序:
# 调用B的__new__》传入B对象,调用A的__new__,调用object.__new__,返回B的实例》返回到B的__new__中》调用B的__init__》调用父类A的__init__,初始化并继承A的属性》继续B的__init__属性重写;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# __del__
使用场景:当需要操作应用对象的时候
而__del__
方法则是当对象被系统回收的时候调用的魔法方法,在对象生命周期调用结束时调用该方法。
Python 采用自动引用计数(ARC)方式来回收对象所占用的空间,当程序中有一个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 1;当程序中有两个变量引用该 Python 对象时,Python 会自动保证该对象引用计数为 2,依此类推,如果一个对象的引用计数变成了 0,则说明程序中不再有变量引用该对象,表明程序不再需要该对象,因此 Python 就会回收该对象。所以大部分时候,都不需要我们手动去删掉不再使用的对象,python的回收机制会自动帮我们做这件事。
本质:每创建一个对象引用计数加1,对象销毁后自动减一,当引用计数为0的时候,自动触发__del__函数
"""
本质:每创建一个对象引用计数加1,对象销毁后自动减一,当引用计数为0的时候,自动触发__del__函数
"""
class A(object):
def __init__(self, a):
print('this is A init')
self.a = a
def __del__(self):
print('this is magic method del')
m = A(1)
# this is A init
n = A(2)
n1 = n
# this is A init
del m # m被删除,这个对象的引用数为0;自动调用其del方法
# this is magic method del
del n # 还有一个n1被引用
del n1
"""
this is A init
this is A init
this is magic method del
this is magic method del
"""
# this is magic method del
# 进一步,查看python的自动回收机制,不用del去手动删除实例
def func(a, b):
x = A(a).a
y = b
return x + y
func(1, 2)
# this is A init
# this is magic method del#因为函数调用返回后,x和y自动被销毁;因此会触发__del__
# 3
"""
this is A init
this is magic method del
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 2.2 控制属性访问
这类魔法方法主要用于再对对象的属性进行访问、定义、修改时起作用。主要有:
__getattr__(self, name)
: 定义当用户试图获取一个属性时的行为。__getattribute__(self, name)
:定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__
)。__setattr__(self, name, value)
:定义当一个属性被设置时的行为。__delattr__(self, name)
:定义当一个属性被删除时的行为。
当初始化属性时如self.a=a
时或修改实例属性如ins.a=1
时本质时调用魔法方法self.__setattr__(name,values)
;
当实例访问某个属性如ins.a
本质是调用魔法方法a.__getattr__(name)
;
当删除对象某个属性如delattr(ins,'a')
本质是调用魔法方法a.__delattr__(name)
class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __setattr__(self, key, value):
print(key,value)
print('this is magic method setattr')
def __getattr__(self, item):
print('getattr:%s'%item)
print('this is magic method getattr')
def __delattr__(self, item):
print('delattr:%s'%item)
print('this is magic method delattr')
>>>m=A(1,2)
a 1
this is magic method setattr # 初始化self.a=a时调用 __setattr__
b 2
this is magic method setattr # 初始化self.b=b时调用 __setattr__
>>>a=m.a
getattr:a
this is magic method getattr # 访问属性a时调用__getattr__
>>>m.b=100
b 100
this is magic method setattr # 修改属性b时调用__setattr__
>>>delattr(m,'a')
delattr:a
this is magic method delattr # 删除属性a时调用__delattr__
>>>print(m.a)
getattr:a
this is magic method getattr
None#属性a被删除,为None
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
上面的代码有一些隐患,因为我们重载了__setattr__
,因此属性初始化时就调用重载后的__setattr__
;但是在初始化属性调用__setattr__
时需要配合实例的属性管理__dict__
来进行,即需要将属性都在self.__dict__
中进行注册,否则实例是访问不到这些属性的。如
>>>print(m.a)
getattr:a
this is magic method getattr
None # 可以看到,并没有初始化成功为a=1,因为重载的__setattr__方法内部尚未将属性在__dict__中注册
#修改上面的__setattr__:
class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __setattr__(self, key, value):
print(key,value)
print('this is magic method setattr')
self.__dict__[key] = value#在__dict__注册实例属性
#super().__setattr__(key,value) 也可以通过继承的方式来实现;
def __getattr__(self, item):
print('getattr:%s'%item)
print('this is magic method getattr')
def f(self):
return self.__dict__#查看属性管理字典
>>>m=A(1,2)
>>>m.a
1
>>>m.f()
{'a': 1, 'b': 2}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
其中,重载__setattr__
时一定要配合__dict__
,或者在添加了自定义的功能后,通过继承来完成该方法,不然容易陷入无线递归的陷阱。
控制属性重载的使用场景:如在初始化属性时先对属性的值进行拦截,进行相应的处理或者判断(比如类型判断,或者范围判断);
class A(object):
def __init__(self,age,sex):
self.age=age
self.sex=sex
def __setattr__(self, key, value):
if key=='age':
if not 0<=value<=100:
raise Exception('age must between 0 and 100')
elif key=='age':
if not (value=='male' or value=='female'):
raise Exception('sex must be male of female')
else:
pass
super().__setattr__(key,value) # 调用父类的方法
>>>m=A(age=102,sex='male')
Exception: age must between 0 and 100
>>>m=A(age=90,sex='hhh')
Exception: sex must be male of female
>>>m=A(age=90,sex='male')
>>>print(m.sex,m.age)
male 90
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.3 容器类操作
有一些方法可以让我们自己定义自己的容器,就像python内置的list,tuple,dict等;
容器分为可变容器和不可变容器,这里的细节需要去了解相关的协议。
- 如果自定义一个不可变容器的话,只能定义
__len__
和__getitem__
; - 定义一个可变容器除了不可变容器的所有魔法方法,还需要定义
__setitem__
和__delitem__
;如果容器可迭代。还需要定义__iter__
主要有以下方法:
__len__(self)
:返回容器的长度__getitem__(self,key)
:当需要执行self[key]的方式去调用容器中的对象,调用的是该方法__setitem__(self,key,value)
:当需要执行self[key] = value时,调用的是该方法。__delitem__(self, key)
:当需要执行 del self[key]时,需要调用该方法;__iter__(self)
:当容器可以执行 for x in container: ,或者使用iter(container)时,需要定义该方法__reversed__(self)
:实现当reversed()被调用时的行为。应该返回序列反转后的版本。仅当序列可以是有序的时候实现它,例如对于列表或者元组。__contains__(self, item)
:定义了调用in和not in来测试成员是否存在的时候所产生的行为。
# __len__
返回容器的长度,在类中定义了__len__
方法 则可以使用 len 获取长度
# 获取列表的长度
class LenStr(object):
def __init__(self, list1):
self.list1 = list1
def __len__(self):
return len(self.list1) + 100
x = [1, 2, 3]
l = LenStr(x)
print(len(l)) # 类中没有定义 __len__ 则不可以使用 len方法
"""
103
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# __getitem__、__setitem__、__delitem__
__getitem__(self,key)
:当需要执行self[key]的方式去调用容器中的对象,调用的是该方法。(定义获取元素的行为,当使用索引操作符[]访问对象时调用。)__setitem__(self,key,value)
:当需要执行self[key] = value时,调用的是该方法。(定义设置元素的行为,当使用索引操作符[]=赋值给对象时调用。)__delitem__(self, key)
:当需要执行 del self[key]时,需要调用该方法。(定义删除元素的行为,当使用del语句删除对象中的元素时调用。)
"""
- `__getitem__(self,key)`:当需要执行self[key]的方式去调用容器中的对象,调用的是该方法。(定义获取元素的行为,当使用索引操作符[]访问对象时调用。)
- `__setitem__(self,key,value)`:当需要执行self[key] = value时,调用的是该方法。(定义设置元素的行为,当使用索引操作符[]=赋值给对象时调用。)
- `__delitem__(self, key)`:当需要执行 del self[key]时,需要调用该方法。(定义删除元素的行为,当使用del语句删除对象中的元素时调用。)
"""
class MyList:
def __init__(self, *args):
self.data = list(args)
def __repr__(self):
"""直接显示对象的值"""
return repr(self.data)
def __getitem__(self, index):
"""获取元素时触发"""
print("__getitem__")
return self.data[index] + 5
def __setitem__(self, index, value):
"""设置元素时触发"""
print("__setitem__")
self.data[index] = value
def __delitem__(self, index):
"""删除元素时触发"""
print("__delitem__")
del self.data[index]
mylist = MyList(1, 2, 3)
print(mylist) # [1, 2, 3]
# 获取元素
print(mylist[0]) # 1
print(mylist[-1]) # 3
# 设置元素
mylist[0] = 4
print(mylist) # [4, 2, 3]
# 删除元素
del mylist[1]
print(mylist) # [4, 3]
"""
[1, 2, 3]
__getitem__
6
__getitem__
8
__setitem__
[4, 2, 3]
__delitem__
[4, 3]
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# __iter__
用于定义一个可迭代对象。如果一个类实现了__iter__
方法并返回了一个迭代器对象,那么这个类的实例就可以被遍历,支持for循环操作。
在__iter__
方法中,需要返回一个迭代器对象,通常是该类的一个实例。这个迭代器对象要实现__next__
方法,用于返回下一个元素的值,并在没有更多元素时抛出StopIteration异常。
当容器可以执行 for x in container: ,或者使用iter(container)时,需要定义该方法
class MyRange:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# 使用自定义的MyRange类
my_range = MyRange(1, 5)
for num in my_range:
print(num)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# __reversed__
当一个对象需要支持反向迭代功能时,可以在该对象的类中定义__reversed__
方法,并使其返回一个反向迭代器。反向迭代器是一个实现了__next__
方法的对象,每次调用__next__
方法可以返回序列中的前一个元素,直到序列结束时抛出StopIteration异常。
class MyReverseIterator:
def __init__(self, data):
self.index = len(data) - 1
self.data = data
def __iter__(self):
return self
def __next__(self):
if self.index < 0:
raise StopIteration
value = self.data[self.index]
self.index -= 1
return value
class MyClass:
def __init__(self):
self.data = [1, 2, 3]
def __reversed__(self):
return MyReverseIterator(self.data)
my_obj = MyClass()
for value in reversed(my_obj):
print(value)
# Output:
# 3
# 2
# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# __contains__
当一个对象需要支持检查某个元素是否在其中时,可以在该对象的类中定义__contains__
方法,并使其返回一个布尔值,表示该元素是否在该对象中。
class MyContainer:
def __init__(self, data):
self.data = data
def __repr__(self):
"""直接显示对象的值"""
return repr(self.data)
def __contains__(self, item):
return item in self.data
my_container = MyContainer([1, 2, 3])
print(my_container)
print(1 in my_container) # True
print(4 in my_container) # False
"""
[1, 2, 3]
True
False
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 面试题:自定义容器
下面举一个例子,实现一个容器,该容器有List的一般功能,同时增加一些其它功能如访问第一个元素,最后一个元素,记录每个元素被访问的次数等
class SpecialList(object):
def __init__(self,values=None):
if values is None:
self.values=[]
else:
self.values=values
self.count={}.fromkeys(range(len(self.values)),0) # {0: 0, 1: 0, 2: 0}
def __getitem__(self, key):#通过obj[key]访问容器内的对象
self.count[key]+=1 # 记录每次访问次数
return self.values[key]
>>>a=SpecialList()
>>>print(len(a)) # 未定义魔法方法__len__,因此无法通过len()调用
TypeError: object of type 'SpecialList' has no len()
>>>b=SpecialList([1,2,3])
>>>print(b[1])#调用__getitem__方法
2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
下面是完整例子:
class MyReverseIterator:
def __init__(self, data):
data.sort()
self.index = len(data) - 1
self.data = data
def __iter__(self):
return self
def __next__(self):
if self.index < 0:
raise StopIteration
value = self.data[self.index]
self.index -= 1
return value
class SpecialList(object):
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
self.count = {}.fromkeys(range(len(self.values)), 0)
def __repr__(self):
"""直接显示对象的值"""
return repr(self.values)
def __len__(self): # 通过len(obj)访问容器长度
return len(self.values)
def __getitem__(self, key): # 通过obj[key]访问容器内的对象
self.count[key] += 1
return self.values[key]
def __setitem__(self, key, value): # 通过obj[key]=value去修改容器内的对象
self.values[key] = value
def __delitem__(self, key): # 通过del obj[key]来删除容器内的对象
del self.values[key]
def __iter__(self): # 通过for 循环来遍历容器
return iter(self.values)
def __next__(self):
# 迭代的具体细节
# 如果__iter__返回时self 则必须实现此方法
if self._index >= len(self.values):
raise StopIteration()
value = self.values[self._index]
self._index += 1
return value
def __reversed__(self): # 通过reverse(obj)来反转容器内的对象
return MyReverseIterator(self.values)
def __contains__(self, item): # 通过 item in obj来判断元素是否在容器内
return item in self.values
def append(self, value):
self.values.append(value)
def head(self):
# 获取第一个元素
return self.values[0]
def tail(self):
# 获取第一个元素之后的所有元素
return self.values[1:]
s_list = SpecialList([1, 2, 3])
s_list.append(1)
s_list.append(2)
print(s_list)
print(len(s_list))
print(s_list.count)
for value in reversed(s_list):
print(value)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
这类方法的使用场景主要在你需要定义一个满足需求的容器类数据结构时会用到,比如可以尝试自定义实现树结构、链表等数据结构(在collections中均已有),或者项目中需要定制的一些容器类型。
# 2.4 类的表示
类的表示相关的魔法方法主要有__str__
、__repr__
和__bool__
__str__
主要是在打印对象print(obj)时,会隐式调用str(obj),即调用类中的__str__
方法;定了该方法就可以通过str(obj)来调用;__repr__
主要式在直接输出对象时的显示,会调用__repr__
方法;定义了该方法就可以通过repr(obj)来调用。
# __str__
、__repr__
这两个方法都是将类显示为字符串;
举例(以datetime模块中的date对象举例):
import datetime
today=datetime.date.today()
>>>print(today)#调用date类的__str__方法来显示
2021-07-04
>>>print(str(today))
2021-07-04
>>>today#调用date类的__repr__来显示
datetime.date(2021, 7, 4)
2
3
4
5
6
7
8
回溯到date类的源码,可以清晰的对上面的输出进行解释:
#date类中的__repr__和__str__方法定义:
def __repr__(self):
"""Convert to formal string, for repr().
>>> dt = datetime(2010, 1, 1)
>>> repr(dt)
'datetime.datetime(2010, 1, 1, 0, 0)'
>>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
>>> repr(dt)
'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
"""
return "%s.%s(%d, %d, %d)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._year,
self._month,
self._day)
def isoformat(self):
"""Return the date formatted according to ISO.
This is 'YYYY-MM-DD'.
References:
- http://www.w3.org/TR/NOTE-datetime
- http://www.cl.cam.ac.uk/~mgk25/iso-time.html
"""
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
__str__ = isoformat
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
如果自定义类中不定义这两个魔法方法,则会用父类或者object类的默认表示方法,举例:
class A(object):
def __init__(self, a):
self.a = a
>>>a = A(1)
>>>print(a)#调用__str__
<__main__.A object at 0x000001540B1F4DF0>
>>>a#调用__repr__
<__main__.A object at 0x000001540B1F4DF0>
>>>str(a)#调用__str__
'<__main__.A object at 0x000001540B1F4DF0>'
>>>print(str(a))#调用__str__
<__main__.A object at 0x000001540B1F4DF0>
2
3
4
5
6
7
8
9
10
11
12
可以看到,如果没有定义__str__
和__repr__
,则会调用默认的魔法方法(object的__str__
和__repr__
)即obj.__class__ object at XXX
;
只定义了__str__
:
class A(object):
def __init__(self,a):
self.a=a
def __str__(self):
return '(%s,%s)'%(self.__class__.__name__,self.a)
>>>a=A(1)
>>>print(a)
(A,1)
>>>a#未定义__repr__,仍旧调用默认的__repr__方法
<__main__.A object at 0x000001540B215790>
2
3
4
5
6
7
8
9
10
只定义了__repr__
class A(object):
def __init__(self, a):
self.a = a
# def __str__(self):
# return '(%s,%s)'%(self.__class__.__name__,self.a)
def __repr__(self):
return '(repr:%s,%s)' % (self.__class__.__name__, self.a)
>>>a = A(1)
>>>a#调用__repr__
(repr:A,1)
>>>print(a)#未定义__str__;转为调用__repr__;
(repr:A,1)
>>>str(a)#未定义__str__;转为调用__repr__;
'(repr:A,1)'
2
3
4
5
6
7
8
9
10
11
12
13
14
从上面可以看出,当自定义类中没有定义 __str__()
和 __repr__()
时,在进行对象的输出时,会调用默认的 __str__()
和 __repr__()
;当类中只包含 __str__()
时,调用 print() 或str()函数进行对象的输出,会调用 __str__()
,直接输出调用默认的 __repr__()
;当类中既包含 __str__()
又包含 __repr__()
时,调用 print() 或str()函数进行对象的输出,会调用 __str__()
,直接输出会调用 __repr__()
;当类中只包含 __repr__()
时,调用 print() 或str()函数进行对象的输出和直接输出都会调用 __repr__()
。
因此,对于自定义的类,建议定义__str__
和__repr__
,以更好的进行交互;其中__str__
可以考虑设计为想转换输出的字符串,在后续str(obj)将对象转为特定的字符串输出时提供一些便利;__repr__
可以考虑设计为输出较为详细的信息,如列名,甚至包括部分关键参数名,这样在开发的时候便于获取对象的准确信息(如sklearn中的机器学习模块就是如此设计)
class A(object):
def __init__(self, a):
self.a = a
def __str__(self):
return '(%s,%s)' % (self.__class__.__name__, self.a)
a = A(1)
print(a)
class A(object):
def __init__(self, a):
self.a = a
# def __str__(self):
# return '(%s,%s)'%(self.__class__.__name__,self.a)
def __repr__(self):
return '(repr:%s,%s)' % (self.__class__.__name__, self.a)
a = A(1)
print(a) # 如果里面定义了__str__方法则会执行__str__ 没有的话则执行__repr__方法
"""
(A,1)
(repr:A,1)
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# __bool__
_bool__
:当调用 bool(obj) 时,会调用 __bool__()
方法,返回 True 或 False:
class A(object):
def __init__(self, a):
self.a = a
def __bool__(self):
return self.a > 10
a = A(1)
print(a)
print(bool(a))
# 如果未定义__bool__,则会调用内置的bool对象。因为继承object
2
3
4
5
6
7
8
9
10
11
12
# 2.5 可调用对象
# __call__
当一个对象需要支持像函数一样被调用时,可以在该对象的类中定义__call__
方法。在调用该对象时,Python会调用该对象的__call__
方法,并将调用时传入的参数传递给__call__
方法。
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __call__(self, a):
self.a = a
m = A(1, 2)
print(m.a, m.b, id(m))
m(100) # 像函数一样直接调用类,本质是调用的__call__方法,不实现__call__方法不能执行实例
print(m.a, m.b, id(m))
"""
1 2 2185670174656
100 2 2185670174656
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__call__
方法的一些使用场景:自建一个简单的装饰器(实际例子如 bottle 框架源码的 cached_property
):
class A(object):
def __init__(self, func):
self.func = func
def __call__(self):
print('this is call')
return self.func() # 调用f这个函数
@A # 语法糖,相当于A(f)
def f():
print('hhh')
f() # 执行call方法
"""
this is call
hhh
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
定义一个对象,对象中具有各类方法,但是核心功能只有一个,可以通过可调用对象来简单且直接的调用,不用每次都实例化对象。
同样还可以用作抽象窗口函数,抽象出使用规则,通过子类的改写其他方法,在不改变原代码的情况下取得不同的结果,比如:
# call子类的改写其他方法,在不改变原代码的情况下取得不同的结果
class CSVDataProcess:
def __init__(self):
pass
def example_one(self):
print(1)
def __call__(self, exmaple):
try:
target_func = getattr(self, exmaple)
target_func() # 执行传递过来的函数
except Exception as e:
print("execute error", e)
if __name__ == "__main__":
csv = CSVDataProcess()
csv("example_one") # 1
#这个例子在子类继承时,子类重新实现example;通过调用子类直接就可以通过__call__来直接调用该子类的example方法;整个代码非常简洁了;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.6 序列化
python中有一个pickle模块来对实例化对象进行序列化;如pickle.loads(obj),pickle.dumps(obj)等;
在序列化的时候也是调用的内置魔法方法:
__getstate__()
: 用于python 对象的序列化,指定在序列化时将哪些信息记录下来__setstate__()
:用于python 对象的反序列化,指定在反序列化时指明如何利用信息
当一个对象需要被序列化为字节流或者字符串时,可以在该对象的类中定义__getstate__
方法并返回一个表示对象状态的字典。当反序列化对象时,需要在该对象的类中定义__setstate__
方法,接收一个表示对象状态的字典,并使用其中的信息来重构对象。
# __getstate__、__setstate__
举例说明:
# 直接通过pickle保存一个对象实例,默认将该实例的属性、方法都保存下来;
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
import pickle # 序列化,反序列化对象
a = A(1, 2)
a_1 = pickle.dumps(a)
print(a_1)
a_2 = pickle.loads(a_1)
print(a_2)
print(a_2.a, a_2.b)
"""
b'\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02ub.'
<__main__.A object at 0x000002CF4439D280>
1 2
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
如果重写__getstate__()__setstate__()
,如下:
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __getstate__(self):
print('this is magic method __getstate__')
return {'a': self.a, 'b': self.b} # 序列化时返回的,即为在反序列化时传入的state
def __setstate__(self, state):
print('this is magic method __setstate__')
self.a = state['a']
self.b = 300
import pickle
a = A(1, 2)
a_1 = pickle.dumps(a) # 调用__getstate__
print(a_1)
a_2 = pickle.loads(a_1) # 调用__setstate__
print(a_2)
print(a_2.a, a_2.b)
"""
this is magic method __getstate__
b'\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02ub.'
this is magic method __setstate__
<__main__.A object at 0x00000218F2F427C0>
1 300
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2.7 反射
我们可以控制怎么使用内置在函数sisinstance()和issubclass()方法 反射定义魔术方法. 这个魔术方法是:
__instancecheck__(self, instance)
__subclasscheck__(self, subclass)
这两个方法定义在元类中在有意义
# __instancecheck__、__subclasscheck__
__instancecheck__
和 __subclasscheck__
是 Python 中用于自定义对象的类型判断的特殊方法。它们能够让我们自定义一个类,使其能够像内置类型一样被 isinstance() 或 issubclass() 函数使用。
class Person:
pass
class Student(Person):
pass
class Teacher(Person):
pass
class University:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
def __getitem__(self, item):
return self.members[item]
def __instancecheck__(self, instance):
return isinstance(instance, Person)
def __subclasscheck__(self, subclass):
return issubclass(subclass, Person)
person = Person()
student = Student()
teacher = Teacher()
university = University([person, student, teacher])
print(isinstance(person, Person)) # True
print(isinstance(student, Person)) # True
print(isinstance(teacher, Person)) # True
print(isinstance(university, Person)) # False
print(issubclass(Person, Person)) # True
print(issubclass(Student, Person)) # True
print(issubclass(Teacher, Person)) # True
print(issubclass(University, Person)) # False
"""
True
True
True
False
True
True
True
False
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 2.8 比较、运算、类型等操作
# __eq__
通过定义各类比较、运算、类型相关的魔法方法,来实现对象之间的各类比较、运算等操作。这类魔法方法非常多,不一一展开,以__eq__
魔法方法举例说明,如果自定义的对象要实现==的比较功能,则必须在类中实现__eq__
:
#__eq__ 方法,可以判断两个对象是否相等
class A(object):
def __init__(self,a,b):
self.a=a
self.b=b
def __eq__(self, other):#这里并没有重写,只是加了一句Print来监控;还是调用的Objecgt的__eq__
print('eq')
return super().__eq__(other)
>>>x=A(1,2)
>>>y=A(1,2)
>>>print(x == y)#如果未定义,则这两个对象就算里面的属性和方法都相同,但仍会返回False
eq
eq#返回两个应该和x与y都调用了有关
False
>>>print(x.__eq__(y))#单向x调用;因为最终仍是调用object的__eq__
NotImplemented
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
自定义__eq__
:
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __eq__(self, other):
print('eq')
if self.a == other.a:
return True
else:
return False
x = A(1, 2)
y = A(1, 3)
print(x == y) # 只要两个实例的a属性相同即返回True
print(x.__eq__(y))
"""
eq
True
eq
True
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# __int__
再举一个__int__
的类型转换的魔法方法
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
x = A(1, 2)
# int(x) # 未定义__int__,就只能按默认的int对象来转换,里面必须为string,float这类;
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __int__(self):
return int(self.a[:3]) # 必须返回为int数字
x = A('345_hhh', 3)
print(int(x))
"""
345
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其它的比较、运算、类型等魔法方法如下:
其它的比较、运算、类型等魔法方法如下:
- 用于比较的魔法方法
https://segmentfault.com/img/bVcTcD8 (opens new window)
- 双目运算符或函数
https://segmentfault.com/img/bVcTcD6 (opens new window)
- 增量运算
https://segmentfault.com/img/bVcTcD3 (opens new window)
- 类型转换
https://segmentfault.com/img/bVcTcD1 (opens new window)
# 总结
这篇文章较为详细的阐述了python中的各类魔法方法及相关demo;但这类方法在python的文档中较为分散,最重要的是鲜有实际的工程示例。但各类方法只有有实际的案例中才会真正体会到其“魔法”之处,比如大大简化代码,提高代码可读性、健壮性等等。在python一些第三方库中,查其源码都可以见到非常多的魔法方法的实际应用,因此当前这篇文章仅是抛砖引玉,真正的使用需要在开源的优秀源码中以及自身的工程实践中不断加深理解并合适应用。
部分参考:
https://blog.csdn.net/weixin_... (opens new window)
https://zhuanlan.zhihu.com/p/... (opens new window)