Python数据模型

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__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看。

你可能感兴趣的:(Python数据模型)