《流畅的Python》Data model(数据/对象模型)
第一章 Data model
⚠️整本书都是讲解Data model,第一章是一个概述。
⚠️不适合初学者。因为special method和meta programming技巧只是Python代码的一部分。不是全部。
目标:学习第一章内容。之后是否深入还是先学习一些其他的知识待定。
纸牌的例子
>>> a = dict(a=1,b=2)
>>> a
{'a': 1, 'b': 2}
>>> a['a'] == type(a).__getitem__(a, 'a')
True
Data model中的special methods name让你的对象执行/实现,支持,交互basic language构架,例如:
- Iteration
- Collections 集合类
- Attribute access
- Operator overloading 重载操作符
- 函数和方法的调用
- Object创建和销毁
- 字符串格式化
- Managed contexts被管理的上下文代码(就是with语法块)
⚠️这些特殊方法,也被叫做magic method,或者dunder method。只是一种叫法。
import collections
# 扑克牌类,每张牌有2个属性:数字/字母rank,花色suit
Card = collections.namedtuple('Card', ['rank', 'suit']) class FrenchDeck(object):
# 2个类属性,储存牌的rank和suit。
ranks = [str(n) for n in range(2, 11)] + list('JQKA') #13种rank
suits = 'spades diamonds clubs hearts'.split() #4种suit
# 实例化一个对象,实例变量_cards(数据属性)是一个list,储存了52张Card(类型是namedtule)
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
# self._cards = []
# for suit in self.suits:
# for rank in self.ranks:
# self._cards.append(Card(rank, suit)) # 因为内置函数len()的实参类型支持是内置的或序列或集合类型。(也包括collections中的类型)
# 所以对自定义的类FrenckDeck,想查看实例对象的元素数量,需要重写__len__
def __len__(self):
print('The length of deck is %s' % len(self._cards))
return len(self._cards) #
# 重写__getitem__
# 因为实例对象deck,使用特殊语法[],解释器就会去找__getitem__这个特殊方法。
# 因为deck[参数],转化为__getitem__(self, 参数),最后调用self._cart[参数], 所以可以支持切片。
def __getitem__(self, position):
return self._cards[position] deck = FrenchDeck()
print(len(deck))
print(deck[:2])
输出:
The length of deck is 52
52
[Card(rank='', suit='spades'), Card(rank='', suit='spades')]
因为__getitem__返回的是一个tuple子类的对象,它是iterable的所以可以使用for:
>>> for card in deck:
... print(card)
...
Card(rank='', suit='spades')
Card(rank='', suit='spades')
...
解释:
for value in variable: pass语法的variable是一个iterator对象,它是iter(object)返回的。因此object有2类可能:
- 是支持迭代协议(有
__iter__()
方法)的集合对象。 - 或是支持序列协议(有
__getitem__()
方法,且数字参数从0
开始。本例的deck实例对象有方法__getitem__()
问题:为每张扑克牌设定一个大小值。然后按照扑克大小排序?
def spades_high(self,card):
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
# 用ranks的索引表示一张卡牌的rank的大小。从0~12
rank_value = FrenchDeck.ranks.index(card.rank)
print(rank_value)
return rank_value * len(suit_values) + suit_values[card.suit]
rank_value变量的取值范围0~12
len(suit_values): 得到4
所以:rank_value * len(suit_values)的值:0 4 8 12 16 20 24 28 32 36 40 44 48
得到的值,根据不同的牌的花色分别再加0,1,2,3。
所以:
2 3 4 5 6 7 8 9 10 J Q k A
club: 0 4 8 12 16 20 24 28 32 36 40 44 48
diamons: 1 5 9 13 17 21 25 29 33 37 41 45 49
hearts: 2 6 10 14 18 22 26 30 34 38 42 46 50
spades: 3 7 11 15 19 23 27 31 35 39 43 47 51
#根据扑克大小排序:
for card in sorted(deck, key=deck.spades_high):
print(card)
sorted(iterable, *, key=None)
使用sorted()返回一个按照大小排序的deck列表:
[Card(rank='', suit='clubs'), Card(rank='', suit='diamonds'), ...略]
in运算符
执行一次迭代搜索
>>> Card('Q', 'hearts') in deck True #True
小结:
- FrenchDeck 是继承object类。(implicitly)? 什么是隐式(即使不写FrenchDeck(object),也会继承object。这是Python3的特征)
- 功能的实现,不是来自继承。而是使用data model和合成的方法来实现的。
- 通过special methods,FrenchDeck的行为就像一个Python自带的序列数据类型。比如本例子的迭代和切片操作。
- French类还可以使用标准库中的模块的方法。
- 感谢合成composition,__len__, __getitem__的实现能够代理/传递所有的工作给一个list object: self._cards。
How Special Methods Are Used?
首先,Special Methods是由解释器调用的。
- 无需写my_object.__len__()这个格式❌,
- 而是写len(my_object) ✅
如果实例对象是user-defined class的实例,解释器会调用你在类定义中实现的__len__这个实例方法。
但是,像list, str, bytearray等等内建类型。解释器为了提高运算速度,Cpython会抄近路,直接调用C语言写的对应的结构体内的值。
特殊方法的调用:不直接体现在代码上:implicit!
例如for语句会调用iter()内置函数返回一个iterator用于迭代。如果参数对象是可迭代的,这个函数会是调用object.__iter__()。
除了元编程,一般很少直接使用special method。只有__init__方法用的较多:在子类中调用superclass。
尽量使用内置函数来代替special method:
- 内置函数是一种扩展,除了调用对应的特殊方法,还有提供其他服务。
- 对应内置类型数据,运行速度更快。可以看本书14.12节。
1.2.1 Emulating Numeric Types 模拟数值类型
自定义的对象,不能使用运算符,但通过特殊方法,我们能够让自定义对象使用运算符,进行运算。
本节展示了几个特殊对象,它们不会被直接的调用,而是被解释器调用。
例子:这是一个二维向量类:
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 __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)
解释器发现+号运输符会去调用__add__这个特殊方法:
>>> import linshi
>>> from linshi import Vector
>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)
解释器发现*号运输符会去调用__mul__这个特殊方法:
1.2.2 String Representation
特殊方法__repr__被内建函数repr调用,它返回一个用字符串的形式表现的对象,以方便观察。这就是String Representation。
上文举例的Vector类,如果没有定义__repr__方法,在控制台输出一个实例对象时,会输出<模块.类 object at 内存地址编号>的格式:
>>> from linshi import Vector
>>> v1 = Vector(2, 4)
>>> v1
<linshi.Vector object at 0x102a0bd60>
只会时
最新文章
- 使用AdvinceInstaller把exe或者msi重新包装成为msi静默安装程序
- 【Android】应用程序启动过程源码分析
- Opencv step by step - 加载视频
- this的使用
- shell脚本调试之工具——bashdb
- delphi 单引号在字符串中使用方法
- iOS 7 如何关闭已打开的应用(App)
- 【转】No JVM could be found on your system解决方法
- [动态规划]P1220 关路灯
- 2017-07-04(sudo wc sort)
- 申请9位数QQ
- hive字段名、注释中文显示问号
- top 自动执行的shell脚本中,使用top -n 1 >; log.txt, 上电自动执行,文件无输出
- matlab json文件解析 需要下载一个jsonlab-1.5
- 第一个微信小程序踩的几个小坑
- 从零开始学 Web 之 CSS3(八)CSS3三个案例
- 学习笔记<;4>;初步控件布局
- (转)Maven学习总结(八)——使用Maven构建多模块项目
- JavaWeb应用项目部署到云ubuntu
- unity, ContentSizeFitter立即生效