Chapter 01 The Python Data Model
一叠Python风格的纸牌
先用一个例子来演示__getitem__
和__len__
魔法方法。
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]
- 用nametuple来创建对象的类,不需要用自定义的方法来绑定属性
- 可以响应
len()
函数返回卡牌数量(__len__
)
>>>deck = French()
>>>len(deck)
52
- 可以索引指定的牌,这是
__getitem__
方法提供的
>>>deck[0]
Card(rank="2", suit="spides")
>>>deck[-1]
Card(rank="A", suit="hears")
- 纸牌也是可以迭代的(
__getitem__
)
>>>for card in deck():
... print(card)
Card(rank="A", suit="hearts")
...
- 因为可以迭代(iterable),解包操作也是支持的(
__getitem__
)
>>>from random import choice
>>>card1 = choice(deck)
>>>x, y = card1
>>>x, y
"7", "spades"
-
__getitem__
方法给slef._cards
授权了[]
操作,我们的卡牌支持切片操作。
>>>deck[:3]
- 迭代通常是隐形的。如果一个集合类型没有
__contains__
方法,那么in
操作符会对进行一次序列性扫描(a sequential scan)。(__getitem__
)
>>>Card("Q", "hearts") in deck
True
怎样使用魔法方法
魔法方法是Python解释器调用的,而不是通过你。不需要写my_object.__len__()
这种写法,写len(my_object)
,Python会调用__len__
。
对于内置的类型,比如list, str, bytearray等等,解释器会使用更快捷的方式而不是魔法方法,因为这样更快。比如在CPython的len()
的实现实际上是返回在PyVarObject
C结构体里的ob_size
字段的值,PyVarObject
是内存中任意可变长度的内置对象。
对于len
为什么不是一个方法,核心开发者Raymond Hettinger的解释是:practicality beats purity。实用胜于纯粹。获取集合类型的元素数量是一个通用操作而且对于这些基本的类型,需要更加高效。
通常情况下,魔法方法的调用是隐性的。比如,for i in x
语句,实际上调用iter(x)
,在这背后的实现是x.__iter__()
,前提是这个方法实现了(前面没有__iter__
没有实现而是用__getitem__
)
当没有自定义的__str__
方法时,会调用__repr__
作为替代,所以在__str__
和__repr__
方法里只能选择一个的话,选择__repr__
。
模拟数学类型
再来另一个例子,用一个类来实现二维向量的表示
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({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__
,当我们使用print
的时候,调用的是str()
,也就是__str__
,这里我们没有重载__str__
方法,这样print
会调用__repr__
>>>v1 = Vector(2, 4)
>>>v2 = Vector(2, 1)
>>>v1 # 调用__repr__()
Vector(2, 4)
>>>print(v1) # 没有__str__,调用__repr__()
Vector(2, 4)
- 重载
__add__
和__mul__
,这两个方法背后是+
,*
操作符:
>>>v1 + v2
Vector(4, 5)
>>>v1 * 3
Vector(6, 12)
# 3 * v1和v1 * v2是报错的,具体我还没会
# TypeError: unsupported operand type(s) for *: 'int' and 'Vector'
网友评论