微信搜索关注“水滴和银弹”公众号,首次获得优质技术干货。用7年的资深后端研发,用简单的方式明确说明技术。
上一句python高级——中如何正确使用魔法方法?(上图)主要介绍了配置和初始化、类表示、访问控制等魔法方法、使用场景。
这句话,我们主要继续介绍剩下的魔法方法:比较操作、容器类操作、可调用对象、序列化等。
比较操作
比较工作的魔法方法主要有:
_ _ CMP _ _ _ _ _ _ EQ _ _ _ _ _ _ LT _ _ _ _ _ GT _ _ _ _ _ _ CMP _ _
从名字中我们可以看出这种魔法方法的作用。需要比较两个对象时,可以为比较操作定义__cmp__。
Class person(对象):
Def__init__(self,uid):
=uid
Def _ _ CMP _ _ (self,其他):
If==o:
Return0
Ifo:
Return1
Return-1
P1=Person(1)
P2=Person(2)
Printp1p2#False
Printp1p2#True
Printp1==p2#False
从例子中我们可以看到比较两个对象的具体逻辑。
如果__cmp__返回大于0的整数(通常为1),如果说明self other __cmp__返回大于0的整数(通常为-1),说明self other为_;在比较谁时,我想使用属性B进行比较,此时__cmp__这个逻辑不能很好地实现,所以只适用于一般的比较逻辑。
那么如何实现复杂的比较逻辑呢?
为此,__eq__、__ne__、__lt__、__gt__
#coding:utf8
Class person(对象):
Def _ _ init _ _ (self,uid,name,salary) :
=uid
=name
=salary
Def _ _ eq _ _ (self,其他):
'对象==判断'''
Return==o
Def _ _ ne _ _ (self,其他):
对象!=判断''''
丽顿!=o
Def _ _ lt _ _ (self,其他):
对象“”的判断基于len(名称)“”
Returnlen()len)
Def _ _ gt _ _ (self,其他):
对\ "\ "对象的判断基于alary \ "\ "
利图罗
P1=Person(1,' zhangsan '1000)
P2=Person(1,' lisi '2000)
P3=Person(1,' wangwu '3000)
Printp1==p1#uid是否相同
Printp1!=p2#uid是否不同
Printp2p3#name长度比较
Printp3p2#salary比较
__eq__
__eq__如前一句所述,您可以与__hash__方法一起检查两个对象是否相同。
但是,在本例中,在判断两个对象是否相同时,实际上会比较名为uid的属性。
__ne__
同样,当需要判断两个对象不相等时,将调用__ne__方法。在这种情况下,根据uid判断。
__lt__
判断一个对象是否比另一个对象小时,将调用__lt__方法。此示例根据name的长度进行比较。
__gt__
同样,在判断一个对象是否大于另一个对象时,将调用__gt__方法。此示例根据salary属性进行判断。
在python 3中,__cmp__因与其他魔法方法在功能上很重而被取消
复。容器类操作
接下来我们来看容器类的魔法方法,主要包括:
是不是很熟悉?我们在开发中多少都使用到过这些方法。
在介绍容器的魔法方法之前,我们首先想一下,Python 中的容器类型都有哪些?
是的,Python 中常见的容器类型有:
这些都是容器类型。为什么这么说?
因为它们都是「可迭代」的。可迭代是因为,它们都实现了容器协议,也就是我们下面要介绍到的魔法方法。
我们看下面这个例子。
# coding: utf8
class MyList(object):
"""自己实现一个list"""
def __init__(self, values=None):
# 初始化自定义list
= values or []
def __setitem__(self, key, value):
# 添加元素
[key] = value
def __getitem__(self, key):
# 获取元素
return [key]
def __delitem__(self, key):
# 删除元素
del [key]
def __len__(self):
# 自定义list的元素个数
return len()
def __iter__(self):
# 可迭代
return self
def next(self):
# 迭代的具体细节
# 如果__iter__返回self 则必须实现此方法
if >= len():
raise StopIteration()
value = []
+= 1
return value
def __contains__(self, key):
# 元素是否在自定义list中
return key in
def __reversed__(self):
# 反转
return list(reversed())
# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])
print my_list[0] # __getitem__
my_list[1] = 20 # __setitem__
print 1 in my_list # __contains__
print len(my_list) # __len__
print [i for i in my_list] # __iter__
del my_list[0] # __del__
reversed_list = reversed(my_list) # __reversed__
print [i for i in reversed_list] # __iter__
在这个例子中,我们自己实现了一个 MyList 类,在这个类中,定义了很多容器类的魔法方法。这样一来,我们这个 MyList 类就可以像操作普通 list 一样,通过切片的方式添加、获取、删除、迭代元素了。
__setitem__
当我们执行 my_list[1] = 20 时,就会调用 __setitem__ 方法,这个方法主要用于向容器内添加元素。
__getitem__
当我们执行 my_list[0] 时,就会调用 __getitem__ 方法,这个方法主要用于从容器中读取元素。
__delitem__
当我们执行 del my_list[0] 时,就会调用 __delitem__ 方法,这个方法主要用于从容器中删除元素。
__len__
当我们执行 len(my_list) 时,就会调用 __len__ 方法,这个方法主要用于读取容器内元素的数量。
__iter__
这个方法我们需要重点关注,为什么我们可以执行 [i for i in my_list]?就是因为我们定义了 __iter__。
这个方法的返回值可以有两种:
在这个例子中,__iter__ 返回的是 self,所以我们需要定义 next 方法,实现自己的迭代细节。
next 方法使用一个索引变量,用于记录当前迭代的位置,这个方法每次被调用时,都会返回一个元素,当所有元素都迭代完成后,这个方法会返回 StopIteration 异常,此时 for 会停止迭代。
在 Python3 中,已不再使用 next 方法,取而代之的是 __next__。
__contains__
从名字也能看出来,这个方法是在执行 1 in my_list 时触发,用于判断某个元素是否存在于容器中。
__reversed__
这个方法在执行 reversed(my_list) 时触发,用于反转容器的元素,具体的反转逻辑我们也可以自己实现。
可调用对象
了解了容器类魔法方法,我们接着来看可调用对象的魔法方法,这个魔法方法只有一个:__call__。
我们看下面这个例子。
# coding: utf8
class Circle(object):
def __init__(self, x, y):
= x
= y
def __call__(self, x, y):
= x
= y
c = Circle(10, 20) # __init__
print c.x, c.y # 10 20
c(100, 200) # 调用instance() 触发__call__
print c.x, c.y # 100 200
仔细看这个例子,我们首先初始化一个 Circle 实例 c,此时会调用 __init__ 方法,这个很好理解。
但是,我们对于实例 c 又做了调用 c(100, 200),注意,此时的 c 是一个实例对象,当我们这样执行时,其实它调用的就是 __call__。这样一来,我们就可以把实例当做一个方法来执行。
如果不好理解,你可以多看几遍这个例子,理解一下。
也就是说,Python 中的实例,也是可以被调用的,通过定义 __call__ 方法,就可以传入自定义参数实现自己的逻辑。
这个魔法方法通常会用在类实现一个装饰器、元类等场景中,当你遇到这个魔法方法时,你能理解其中的原理就可以了。
序列化
我们知道 Python 提供了序列号模块 pickle,当我们使用这个模块序列化一个实例时,也可以通过魔法方法来实现自己的逻辑,这些魔法方法包括:
我们来看下面的例子。
# coding: utf8
class Person(object):
def __init__(self, name, age, birthday):
= name
= age
= birthday
def __getstate__(self):
# 执行 时 忽略 age 属性
return {
'name': ,
'birthday':
}
def __setstate__(self, state):
# 执行 时 忽略 age 属性
= state['name']
= state['birthday']
person = Person('zhangsan', 20, date(2017, 2, 23))
pickled_person = (person) # __getstate__
p = (pickled_person) # __setstate__
print p.name, p.birthday
print p.age # AttributeError: 'Person' object has no attribute 'age'
__getstate__
在这个例子中,我们首先初始了 Person 对象,其中包括 3 个属性:name、age、birthday。
当我们调用 (person) 时,__getstate__ 方法就会被调用,在这里我们忽略了 Person 对象的 age 属性,那么 person 在序列化时,就只会对其他两个属性进行保存。
__setstate__
同样地,当我们调用 (pickled_person) 时,__setstate__ 会被调用,其中入参就是 __getstate__ 返回的结果。
在 __setstate__ 方法,我们从入参中取得了被序列化的 dict,然后从 dict 中取出对应的属性,就达到了反序列的效果。
其他魔法方法
好了,以上介绍的这些,就是我们平时遇到比较多的魔法方法。
剩下的魔法方法还有很多,主要包括数值处理、算术操作、反射算术操作、增量赋值、类型转换、反射这几类,由于我们在开发中很少会见到,这里就不再过多介绍了,当遇到时,我们直接查阅文档了解即可。
总结
这篇文章,我们主要介绍了关于比较操作、容器类、可调用对象、序列化等魔法方法。
其中,比较操作的魔法方法,可以用于自定义实例的比较逻辑。容器类魔法方法,可以帮我们实现一个自定义的容器类,然后我们就可以像操作 list、dict 那样,方便地去获取容器里的元素、迭代数据等等。可调用对象魔法方法,可以把一个实例当做方法来调用。序列化的魔法方法,可以修改一个实例的序列化和反序列化逻辑。
Python 的魔法方法正如它的名字一样,如果使用得当,我们的类就像被添加了魔法一样,变得更易用。我们可以使用这些魔法方法,帮我们实现一些复杂的功能,例如装饰器、元类等等。
微信搜索关注「水滴与银弹」公众号,第一时间获取优质技术干货。7年资深后端研发,用简单的方式把技术讲清楚。