熟悉 Python 的朋友都知道,Python 的语法非常的灵活和简洁,如:
- 判断一个列表的长度直接使用 len(list)
- 判断一个元素是否在对象中,可以用 item in object
- 遍历一个列表,可以使用 for item in list
然而,很多人只知道对 str list dict 等 Python 内建的类型可以这样操作,不知道对于我们自定义的类型,其实也可以这样操作。
能够让我们如此操作自定义类型的秘密武器就是 Python 的特殊方法,通过实现特殊方法,我们自定义的数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码,或者说,更 Pythonic 的代码。
熟悉 python 的朋友都知道,python 的语法非常的灵活和简洁,如
- 判断一个列表的长度直接使用
len(list)
- 判断一个元素是否在对象中,可以用
item in object
- 遍历一个列表,可以使用
for item in list
然而,很多人只知道对str list dict
等 python 内建的类型可以这样操作,不知道对于我们自定义的类型,其实也可以这样操作。
能够让我们如此操作自定义类型的秘密武器就是 python 的特殊方法
,通过实现特殊方法,我们自定义的数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码,或者说,更 Pythonic 的代码。
__init__
构造函数,相信这个方法大家一定很熟悉,当我们实例化一个类的时候,构造函数会被自动执行。因此我们可以在构造函数中进行一些实例变量的声明和初始化。
class MyObject: def __init__(self): print("实例化类的时候,会自动执行") >>> mo = MyObject() 实例化类的时候,会自动执行
__del__
析构函数,这个方法可能有些人不太熟悉,与__init__
对应的,当一个实例结束的时候会执行这个函数,因此可以在这个函数中执行一些结束的工作,比如关掉网络请求的会话等。
除了在实例被回收的时候会被自动执行,也可以通过del
方法调用,主动释放实例。
class MyObject: def __init__(self): print("实例化类的时候,会自动执行") def __del__(self): print("释放类的时候,会自动执行") >>> mo = MyObject() 实例化类的时候,会自动执行 >>> del mo 释放类的时候,会自动执行
说到实例被释放,你知道 python 是怎样自动进行垃圾回收的吗?欢迎在留言区进行讨论。
__str__
和__repr__
Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过 __repr__
和 __str__
来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的。这就是数据模型中存在特殊方法 __repr__
和 __str__
的原因。
那么这两个方法有什么异同之处呢,我们通过代码来分析一下:
# 没有实现__repr__ 和 __str__两个方法 class ReprAndStr: def __init__(self): pass >>> r = ReprAndStr() >>> r >>> print(r) # 直接调用实例和 print 实例,打印的内容可能不太适合人类阅读
# 实现 __str__,没有实现__repr__ class ReprAndStr: def __init__(self): pass def __str__(self): return "I am a class for you" >>> r = ReprAndStr() >>> r >>> print(r) I am a class for you # 直接调用实例没有任何变化 # print 实例,调用的是__str__方法
# 实现 __repr__,没有实现__str__ class ReprAndStr: def __init__(self): pass def __repr__(self): return "I am a class" >>> r = ReprAndStr() >>> r I am a class >>> print(r) I am a class # 直接调用实例,调用的是__repr__方法 # print 实例,调用的也是__repr__方法
# 同时实现 __repr__ 和 __str__ class ReprAndStr: def __init__(self): pass def __repr__(self): return "I am a class" def __str__(self): return "I am a class for you" >>> r = ReprAndStr() >>> r I am a class >>> print(r) I am a class for you # 直接调用实例,调用的是__repr__方法 # print 实例,调用的是__str__方法
通过上面四组代码的对比,应该比较清晰地得出结论:
- 实例化一个类后, 如果直接调用这个实例,那么调用的是
__repr__
方法 - print 这个实例,调用的是
__str__
方法,如果__str__
方法没有实现,那么会去调用__repr__
方法
因此,如果上面两个方法,只实现一个的话,尽量选择__repr__
方法
__iter__
、__next__
如果想让自己定义的类可迭代,实现for item in class
这样的调用,就需要用到__iter__
和__next__
这两个特殊方法。
定义__iter__
方法表示这个类是一个迭代器。但是它只在迭代开始的时候运行一次。返回的是对象本身。接下来就是循环调用 __next__
,直到遇到 raise StopIteration
为止。
下面使用斐波那契数列的实现来举例说明
class Fib: def __init__(self, max): self.a = 0 self.b = 1 self.max = max def __iter__(self): return self def __next__(self): fib = self.a if fib > self.max: raise StopIteration self.a, self.b = self.b, self.a + self.b return fib >>> fib = Fib(10) >>> for f in fib: print(f) 0 1 1 2 3 5 8
__len__
通过在对象中实现__len__
方法,即可像list
一样使用len()
方法调用自定义对象
class MyList: def __init__(self): self.size = 10 def __len__(self): return self.size >>> ml = MyList() >>> len(ml) 10
__contains__
通过实现__contains__
方法可以使用in
操作符判断一个元素是否在对象中
class MyIn: def __init__(self): self.list = [1, 2, 3] def __contains__(self, item): return item in self.list >>> mi = MyIn() >>> 1 in mi True >>> 4 in mi False
Tips:如果不实现__contains__
方法,只要对象实现了可迭代,也可以使用in
操作, 此时会顺序迭代一遍对象进行判断。
在上面的斐波那契数列中,并没有实现__contains__
方法,但是它可迭代,因此也可以使用in
>>> fib = Fib(10) >>> 3 in fib True
注意:这里有会一个问题,如果你先实例化了一个fib=Fib()
,并且执行了for f in fib
的操作,再判断3 in fib
,此时结果将是False
. 只有重新实例化后,判断结果才是True
>>> fib = Fib(10) >>> for f in fib: print(f) 0 1 1 2 3 5 8 >>> 3 in fib False >>> 3 in Fib(10) True
你知道这个是什么原因吗?欢迎在留言区进行讨论。
__setitem_
、__getitem__
、__delitem_
通过实现__setitem__
方法,可以像字典一样给对象赋值,obj[key]= value
通过实现__getitem__
方法,可以像字典一样通过[]
取值,obj[key]
通过实现__delitem__
方法,可以像字典一样使用del
删除元素,del obj[key]
class MyDict: def __init__(self): self.dict = dict() def __setitem__(self, key, value): self.dict[key] = value def __getitem__(self, item): return self.dict[item] def __delitem__(self, key): del self.dict[key] def __contains__(self, item): return item in self.dict def __len__(self): return len(self.dict) >>> md = MyDict() >>> md['a'] = 'aaa' >>> md['a'] aaa >>> md['b'] = 'bbb' >>> len(md) 2 >>> 'b' in md True >>> del md['b'] >>> len(md) 1 >>> 'b' in md False
能够像调用内建类型一样调用自己定义的对象,是不是感觉很高大上,像上面这样的特殊方法还有很多,比如:
- 通过实现
__add__
特殊方法,可以支持加法运算符+
- 通过实现
__bool__
特殊方法,可以支持bool()
调用
更多特殊方法的使用请参考官方文档的 Data model.
自定义数据类型与内置类型的差别虽然我们可以使用 python 的这些特殊方式调用自定义类型,但是他们的执行方式还是有所不同的。
比如,在 python 中,使用len()
方法调用内置类型str
,list
等,Cpython 解释器并不会去调用__len__
方法,而是直接从一个 C 结构体里读取对象的长度,这样的操作更加高效,而调用自定义类型时,就是去调用自定义对象内的__len__
方法。因此,同样的方法,调用内建类型和自定义类型时的执行方式是不一样的。Cpython 解释器对 Python 的内建类型进行了优化。
虽然len()
方法可以让 Python 自建的类型走后门,但还是可以让自定义类型也能用此方法调用,这种处理方式也算是在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点。
你还知道哪些对 python 内置类型做了优化的方法吗?欢迎在留言区讨论
本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。
阅读全文: http://gitbook.cn/gitchat/activity/5d6a99621e6dd75f4f43034f
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。