什么是特殊方法
Python提供了一些特殊方法,有了这些特殊方法,你可以自定义类/对象。
使用特殊方法自定义类有2个好处:
- 利用Python语言的一致性。如果有人使用你的自定义类,不需要特别记方法名。
- 可以更加方便的使用Python标准库。
特殊方法的分类
迭代
__iter__
: 当python解释器遇到for i in x这个语句时,背后调用的是__iter__()__reversed__
__next__
__missing__
:当__getitem__碰到找不到键的时候,Python就会自动调用它。
集合类
__len__
:在执行len(my_object)时,Python解释器背后调用的是__len__()__getitem__
:在执行my_object[i]时,Python解释器背后调用的是__getitem()__setitem__
__delitem__
__contains__
属性访问
__getattr__
__getattribute__
__setattr__
__delattr__
__dir__
__get__
__set__
__delete__
运算符重载
__abs__
__bool__
:bool(x)背后是x.bool()的结果。如果不存在__bool__方法,那么bool(x)会尝试调用x.len(),如果返回0,则bool会返回false;否则会返回True。__complex__
__int__
__float__
__hash__
__index__
函数和方法的调用
__call__
对象的创建和销毁
__new__
__init__
__del__
__prepare__
__instancecheck__
__subclasscheck__
字符串表示形式和格式化
__repr__
:以便于开发者理解的方式返回对象的字符串表示形式。%r 可用来获取对象的字符串表示形式。例如,在requests
库中,Response类的__repr__
方法如下:
def __repr__(self):
# <Response [200]>
return f"<Response [{self.status_code}]>"
__str__
:以便于用户理解的方式返回对象的字符串表示形式。在str()函数时被调用,或者是在print函数打印一个对象的时候才被调用。如果一个对象没有__str__函数,而Python又需要调用它的时候,解释器会用__repr__作为替代。例如,在requests
库中,Response类的__str__
方法如下:
def __str__(self):
return self.content.decode(self.encoding)
__format__
:会被内置的format()函数和str.format()方法调用,使用特殊的格式代码显示对象的字符串表示形式。__bytes__
:返回对象的字节序列表示形式。
管理上下文
__enter__
__exit__
使用特殊方法实现一个自定义序列
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
# 实现一个扑克牌类
class FrenchDeck:
# 扑克牌取值
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
# 扑克牌花色
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
# 排列组合生成一套扑克牌, 用列表来存储
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
# 通过获取列表的长度,从而实现FrenchDeck的长度获取
# 将具体实现代理给列表
return len(self._cards)
def __getitem__(self):
# 通过获取列表的元素,从而实现FrenchDeck特定元素的获取
# 将具体实现代理给列表
return self._cards[position]
使用特殊方法实现一个向量类
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
映射的弹性键查询
有的时候为了方便起见,就算某个键在映射里不存在,我们也希望在通过这个键读取值得时候能得到一个默认值。
# 在查询的时候,把非字符串得键转换为字符串
# 继承dict
class StrKeyDict0(dict):
# missing方法的职责:如果key在字典不存在,而key又是一个数值类型,那么可以试图将转换成str,再次查找是否存在
# 如果key在字典中不存在,而key又是一个str,那么在missing中即使转换成str,也没有发生任何变化,key还是找不到
def __missing__(self, key):
# 为什么要做类型判断:
# 如果key的类型是str,但是又调用到了__missing__方法,说明该key在字典中确实不存在
# 如果不做类型判断,那么又会调用self[str(key)],str(key)不存在,又会调用__missing__,那么就会陷入无限递归
if isinstance(key, str):
raise KeyError(key)
# str(key)在字典中存在,返回value
return self[str(key)]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
# 为什么要有contains
# 因为在 for i in x,有可能i可能是数值,那么为了保证数值键也可以被查询到,所以实现了__contains__
def __contains__(self, key):
# 这里没有用 key in self 而用了 self.keys的原因是
# 如果是 for key in self,那么会调用自身的__contains__方法,就会陷入递归调用
return key in self.keys() or str(key) in self.keys()