1.1 __getitme__和__len__方法
import collections
from randomimport choice # 序列中随机选出一个元素
card = collections.namedtuple('card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(i)for iin range(2,11)] +list('JQKA')
suits ='spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [card(rank, suit)for suitin self.suits
for rankin self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, item):
return self._cards[item]
因为__getitem__方法把[]操作交给了self._cards列表,所以我们的deck类自动支持切片操作
print(deck[:3) # [card(rank='2', suit='spades'), card(rank='3', suit='spades'), card(rank='4', suit='spades')]
只获取A的牌,取出所以12的那张牌,然后每隔13张获取一张牌
print(deck[12::13]) # [card(rank='A', suit='spades'), card(rank='A', suit='diamonds'), card(rank='A', suit='clubs'), card(rank='A', suit='hearts')]
另外,仅仅实现了__getitem__方法,就变成可迭代的了
for cardin deck:
print(card)
for cardin deck:
print(card)
字符串表示形式:
python中有一个内置的函数repr,它能把一个对象用字符串形式表达出来以便辨认,这就算‘字符串表示形式’, repr就是通过__repr__这个特殊方法得到一个对象的字符串表示形式,如果没有实现__repr__,当我们在控制台打印实例是,得到字符串可能会是<__main__.Vector object at 0x00672690>
def __repr__(self):
return 'Vector(%r,%r)' %(self.x, self.y)
%r来获对象各个属性的标准字符串表示形式 # Vector(1,'3') Vector(1,3) 会显示是字符串还是数值
__repr__和__str__的区别在于,后者实在str()函数被使用,或实在print函数打印一个对象时才被调用,并且它返回的字符串对终端用户更友好。
如果你想实现这两个特殊方法的一个,__repr__是更好的选择,因为如果一个对象没有__str__函数,而python又需要调用它的时候,解释器会用__repr__作为代替。
算术运算符:
通过__add__和__mul__,提供了 + 和 * 这两个算术运算符。值得注意的是,这两个方法的返回值都是新创建的对象,被操作的两个值(self或other)还是原封不动,代码里只是读取了它们的值而已,算术运算符的基本原则就是不改变操作对象,而是产出一个新的值。
自定义的布尔值:
尽管python理由bool类型,但实际上人核对下都可以用于需要布尔值的上下文中(比如if 或while语句,或者and、or、not运算符)。为了判定一个值X为真还是为假,python会调用bool(x),这个函数只能返回True或False。
默认情况下,我们自定义的类的实例总被认为是真的,除非这个类对__bool__或者__len__函数有自己的实现。bool(x)的背后是调用x.__bool__()的结果;如果不存在__bool__方法,那么bool(x)会尝试调用x.__len__(),如果返回0,则bool会返回False;否则返回True。
我们对__bool__的实现很简单,如果一个向量的模是0,那么就返回False,其他情况返回True。因为__bool__函数的返回类型应该是布尔值,所以我们通过bool(abs(self))把模变成了布尔值。
如果想让Vector.__bool__更加高效,可以采用
def __bool__(self):
return bool(self.x or self.y)
它不那么易读,但却能省掉从abs到__abs__到平方在平方根这些中间步骤,通过bool把返回类型显式转换为布尔值是为了符合__bool__对返回值的规定,因为or运算符可能会返回x或y本身的值,若x的值等价于真,则or返回x的值;否则返回y的值
特殊方法一览:
跟运算符无关的特殊方法
类别 方法名
字符串/字节序列表现形式: __repr__、__str__、__format__、__bytes__
数制转换: __abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__
集合模拟: __len__、__getitem__、__setitem__、__delitem__、__contains__
迭代枚举: __item__、__reversed__、__next__
可调用模拟:__call__
上下文管理:__enter__、__exit__
实例创建和销毁:__new__、__init__、__del__
属性管理:__getattr__、__getattribute__、__setattr__、__delattr__、__dir__
属性描述符:__get__、__set__、__delete__
跟类相关的服务:__prepare__、__instancecheck__、__subclasscheck__
跟运算符相关的特殊方法
一元运算符:__neg__ -、__pos__ + 、__abs__、abs()
众多比较运算符:__lt__ < 、__le__ <= 、__eq__ == 、__ne__ != 、__gt__ > 、 __ge__ >= 、
算数运算符:__add__ + 、 __sub__ - 、 __mul__ * 、__truediv__ / 、__floordiv__ // 、__mod__ %、__divmod__ divmod()、__pow__ ** 或pow()、__round__ round()
反向算术运算符:__radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、
增量赋值算术运算符:__iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__
位运算符:__invert__ ~、__lshift__ << 、__rshift__ >> 、__and__ &、__or__ | 、__xor__ ^ 、
反向位运算符: __rlshift__、__rrshift__、__rand__、__rxor__、__ror__
增量赋值位运算符: __ilshift__、__irshift__、__iand__、__ixor__、__ior__
当交换两个操作数的位置时,就会调用反向运算符(b * a 而不是 a * b),增量赋值运算符则是一种把中缀运算符变成赋值运算的捷径(a = a * b 就变成 a * = b)
为什么len不是普通方法
如果x是内置类,那么len(x)的速度很快,原因是CPython会直接从一个C结构体里读取对象长度,完全不会调用任何方法。abs也是同理
小结
通过实现特殊方法,自定义数据类型可以表现的跟内置类型一样,从而写出更具Python风格的代码
Python对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过__repr__和__str__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看。