这一章主要通过几个例子向读者展示了怎么通过定义特殊方法,使自定义的数据类型可以表现的跟内置类型一样,写出更具有Python
风格的代码。
Python
风格的纸牌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):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
由于定义了__len__
方法,所以,当实例化一摞纸牌后,可以len()
来查看这个摞纸牌有多少张。
>>> deck = FrenchDeck()
>>> len(deck)
52
如果没有定义__len__
方法,运行上述代码则会报错TypeError: object of type 'FrenchDeck' has no len()
同样的,由于定义了__getitem__
这个特殊方法,可以使用deck[0]
的方式从这摞扑克中抽取任意一张。
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[1]
Card(rank='3', suit='spades')
如果没有定义__getitem__
个特殊方法,同样也会报错:TypeError: 'FrenchDeck' object is not subscriptable
同时由于实现了__getitem__
方法,这摞扑克也变成可迭代的了,并且也支持切片操作。
>>> for card in deck:
print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
通过实现 __len__
和__getitem__
这两个特殊方法, FrenchDeck
就跟一个 Python 自有的序列数据类型一样,可以体现出 Python
的核心语言特性(例如迭代和切片)。同时这个类还可以用于标准库中诸如random.choice
、 reversed
和 sorted
这些函数。
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)
__repr__
Python
有一个内置的函数叫 repr
,它能把一个对象用字符串的形式表达出来以便辨认,这
就是“字符串表示形式”。 repr
就是通过 __repr__
这个特殊方法来得到一个对象的字符串
表示形式的。如果没有实现 __repr__
,当我们在控制台里打印一个向量的实例时,得到的
字符串可能会是
。通过定义__repr__
方法,我们就可以让这个对象的字符串表示形式显示成我们想要任何样子。
__repr__
和 __str__
的区别在于,后者是在 str()
函数被使用,或是在用 print
函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。如果你只想实现这两个特殊方法中的一个, __repr__
是更好的选择,因为如果一个对象没有 __str__
函数,而 Python
又需要调用它的时候,解释器会用 __repr__
作为替代。
__add__
和 __mul__
通过__add__
和 __mul__
,该向量类的实例化对象可以实现 + 和 * 。
更多的特殊方法可以参考python文档中的Data Model
部分https://docs.python.org/3/reference/datamodel.html
类别 | 方法名 |
---|---|
字符串/字节序列表示形式 | __repr__ 、__str__ 、 __format__ 、__bytes__ |
数值转换 | __abs__ 、__bool__ 、__complex__ 、 __int__ 、__float__ 、 __hash__ 、__index__ |
集合模拟 | __len__ 、__getitem__ 、__setitem__ 、 __delitem__ 、__contains__ |
迭代枚举 | __iter__ 、__reversed__ 、__next__ |
可调用模拟 | __call__ |
上下文管理 | __enter__ 、__exit__ |
实例创建和销毁 | __new__ 、__init__ 、__del__ |
属性管理 | __getattr__ 、__getattribute__ 、__setattr__ 、 __delattr__ 、__dir__ |
属性描述符 | __get__ 、__set__ 、__delete__ |
跟类相关的服务 | __prepare__ 、__instancecheck__ 、__subclasscheck__ |