第一章 Python 数据模型
python最好的品质是一致性
python解释器碰到特殊句法时,会使用特殊方法去激活一些基本的对象操作
这些特殊的方法以两个下划线开头,以两个下划线结尾,如__getitem__
、__len__
等
所谓的特殊句法,如通过下标访问列表,获取自定义类的元素的个数、重构操作符等等
__getitem__
的读法: dunder getitem
随着学习的推进,对这一点的理解会逐渐加深......
我认为 ,特殊方法的实现,就是运算符的重构......
本章通过两个例子,来说明特殊用法
- 纸牌的例子
- 向量的例子
注:特殊方法是为了让编译器调用的,自己不需要调用。如没有obj.__len__()这种写法,应该使用len(obj)。此外, 不要自己想当然地随意添加特殊方法。
1.1 python风格的纸牌
创建一个纸牌类,实现
__getitem__
和__len__
两个特殊方法
代码如下
import collections
Card = collections.namedtuple('Card', ['rank', 'suit']) # 可以用来创建只有少数属性没有方法的类
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA') # 创建一个list 存放2-A
suits = 'spades diamonds clubs hearts'.split() # 花色: 黑桃、红桃、方块、梅花
# split默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等。
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]
创建一个纸牌类之后,对上述类可以进行操作,代码如下:
def run_card():
# 打印输出通过collections.namedtuple创建的类、以及类型
test_card = Card('7', 'diamonds')
print(test_card)
print(type(test_card))
deck = FrenchDeck()
print(deck.ranks)
print(deck.suits)
print(len(deck)) # 获取FrenchDeck类中元素的个数 调用__len__
print(deck[8]) # 根据下标获取FrenchDeck类中的元素 调用__getitem__
# 通过python 内置的方法进行随机选择,而不必重新写一个对象
from random import choice
print(choice(deck)) # 随机从类中选择一个元素
print(choice(deck))
# 可以使用自动切片,因为__getitem__方法把[]操作给了self._cards,所以可以使用切片
print(deck[:3]) # 下标为0、1、2
print(deck[12::13]) # 抽出索引是12的牌, 步长为13,一次取1个
# 可迭代,因为有__getitem__,可以进行迭代、逆迭代
for card in deck:
print(card)
# 反向迭代
for card in reversed(deck):
print(card)
# 由于是可迭代的,因此可以使用 in,
# 集合类型没有实现__contains__方法,那么in运算符就会按顺序进行一次迭代搜索。
print(Card('Q', 'hearts') in deck)
print(Card('r', 'hearts') in deck)
# 排序 因为可以迭代,因此可以进行排序
for card in sorted(deck, key=spades_high): # spades_high为排序策略的方法名
print(card)
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) # 花色的大小定义
def spades_high(card):
rank_value = FrenchDeck.ranks.index(card.rank) # 元素在类中的下标
# print(card.rank +';'+ card.suit)
# print('rank_value:'+str(rank_value))
# print('len:'+ str(len(suit_values)))
# print('*=' + str(rank_value * len(suit_values)))
return rank_value * len(suit_values) + suit_values[card.suit]
FrenchDeck
类隐式继承了Object
类,但是功能却不是继承而来的
我们通过数据模型和一些合成来实现这些功能
通过实现__len__
和__getitem__
这两个特殊方法,FrenchDeck
类就与python自有的序列数据类型一样,可以体现出python的核心语言特性
FrenchDeck
类还可以用于标准库中random.choice、reversed、sorted这些函数
对合成的运用可以使得__len__
、__getitem__
的具体实现可以代理给self._cards
这个列表
1.2 如何使用特殊方法-通过创建一个向量类的例子
实现一个二维向量类
Vector
。
当然向量相关的运算在类中要进行实现,如输出(+
)、取模(abs
)、向量与数的乘法(*
)、布尔判断(bool
)
输出:通过内置函数
repr
,可以把一个对象用字符串形式表示出来以便辨认,这就是"字符串表示形式"
repr
是通过特殊方法__repr__
来实现的,
如果没有实现这个特殊方法,<Vector object at 0x10e100070>
如果使用%
来表示字符串,repr
是通过%r
来表示的
如果使用!
来表示字符串,repr
是通过!r
来表示的
__repr__
所返回的字符串应该准确、无歧义,并且尽可能表达出如何用代码创建出这个被打印的对象
__repr__
和__str___
的区别在于:后者是在str()
被使用或者用print()
打印一个对象时才被调用
由于python在调用str()
时,如果没有发现__str__
就会调用__repr__
,因此优先创建__repr__
加法(
+
):Vector(2, 3) + Vectotr(3, 5) = Vector(5, 8)
采用特殊方法__add__
:这个特殊方法返回的是一个新的对象,被操作的两个对象原封不动。
向量与数的相乘(
*
):Vector(2, 3) * 2 = Vector(4, 6)
、Vector(4, 6) * (-2) = Vector(-8, -12)
采用特殊方法__mul__
:这个特殊方法同__add__
一样,都是返回一个新的对象。
取模运算(
abs
):所谓的取模:就是元素的平方和再开方,即二范数 或 欧几里得范数
采用特殊方法__abs__
abs
是一个内置函数,如果输入是整数或者浮点数,返回的是其绝对值;如果输入的是一个复数。返回的是复数的模
因此在Vector
这个类中,通过调用abs
,应当返回的是向量的模。
布尔运算(bool):这个函数只能返回
True
、False
默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对__bool__
或__len__
有自己的实现,
bool(x)
是调用x.__bool__()
的结果,如果不存在__bool__
,会调用__len__
,如果返回0,bool
就返回False
,否则,返回True
Vector
类的实现如下:
from math import hypot # hypot 是用来求欧几里得范数的
class Vector:
def __init__(self, x=0, y=0): # 定义了 x y 应该是整数类型
self.x = x
self.y = y
def __repr__(self): # __repr__ 中要使用 %r 保存原格式 %r可以用来获取对象各个属性的标准字符串表示形式
return 'Vector(%r, %r)' % (self.x, self.y) # 此时输出的应该为 Vector(3, 4)
def __abs__(self):
return hypot(self.x, self.y) # 返回欧几里得范数 就是向量的模
# 如果模是0 返回False 否则返回True
# 法1
# def __bool__(self):
# return bool(abs(self))
# 法2 更高效,省掉了abs到__abs__到平方再到平方根这些过程
def __bool__(self):
return bool(self.x or self.y) # or运算可能返回x或y本身的值,若x的值为真,则or返回x的值,否返回y的值
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)
对
Vector
类的功能的调用,代码如下:
def run_vector():
vector1 = Vector(3, 4)
print('向量1=%r' % vector1)
print('向量的模长= %f' % abs(vector1))
vector2 = Vector(1, 2)
print('向量2 = %r' % vector2)
print('向量1 + 向量2 = %r' % (vector1 + vector2))
# print('向量1 * 3 = %r' % (vector1 * 3)) # 数在后,向量在前,与__mul__一致
print('向量1的bool = %r' % bool(vector1))
1.3 特殊方法汇总
python语法参考手册中的 Data Model列出了83个特殊方法的名字。下面列出概况。
下表的分类方式与官方文档不同
与运算符无关的特殊方法:
类别 | 方法名 |
---|---|
字符串/字节序列表示形式 | __repr__ 、 __str__ 、 __format__ 、 __bytes__ |
数值转换 | __abs__ 、 __bool__ 、 __complex__ 、 __int__ 、 __float__ 、 __hash__ 、 __index__ |
集合模拟 | __len__ 、 __getitem_ 、 __seitem__ 、 __delitem__ 、 __contains__ |
迭代枚举 | __iter__ 、 __reversed__ 、 __next__ |
可调用模拟 | __call__ |
上下文管理 | __repr__ 、 __repr__ |
实例创建和销毁 | __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__ |
反向位运算符 | __rlshift__ 、 __rrshift__ 、 __rand__ 、 __rxor__ 、 __ror__ |
增量赋值位运算符 | __ilshift__ 、 __irshift__ 、 __iand__ 、 __ixor__ 、 __ior__ |
增量赋值运算符:
a *= b
反向算术运算符,当两个操作数交换了前后位置,此时想要得到一致的结果,需要调用反向算术运算符