第一章 Data model

⚠️整本书都是讲解Data model,第一章是一个概述。

⚠️不适合初学者。因为special method和meta programming技巧只是Python代码的一部分。不是全部。

目标:学习第一章内容。之后是否深入还是先学习一些其他的知识待定。

纸牌的例子

可以把data model是对Python的框架的描述。
 
无论在哪种框架下写程序,程序员都会花费大量时间操作implement很多的由框架自身调用的方法。
当你使用Python data model时也是一样的。
Python解释器调用特殊方法来运行基本的对象的操作,  这种特殊方法经常被special syntax激活。这种特殊方法的名字的写法:__xxx__。
例如:
object.__getitem__(self, key)这是一个特殊方法。a是一个dict,我们希望读取这个对象的key"a"对应的值。下例使用了a["a"],一个特殊的语法。解释器发现这个语法 就会调用特殊方法type(a).__getitem(a, "a"),来完成相应的读取操作。
>>> 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__这个特殊方法:

文档3.3.8给出了可以使用的模拟数字类型

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>

只会时

最新文章

  1. 使用AdvinceInstaller把exe或者msi重新包装成为msi静默安装程序
  2. 【Android】应用程序启动过程源码分析
  3. Opencv step by step - 加载视频
  4. this的使用
  5. shell脚本调试之工具——bashdb
  6. delphi 单引号在字符串中使用方法
  7. iOS 7 如何关闭已打开的应用(App)
  8. 【转】No JVM could be found on your system解决方法
  9. [动态规划]P1220 关路灯
  10. 2017-07-04(sudo wc sort)
  11. 申请9位数QQ
  12. hive字段名、注释中文显示问号
  13. top 自动执行的shell脚本中,使用top -n 1 &gt; log.txt, 上电自动执行,文件无输出
  14. matlab json文件解析 需要下载一个jsonlab-1.5
  15. 第一个微信小程序踩的几个小坑
  16. 从零开始学 Web 之 CSS3(八)CSS3三个案例
  17. 学习笔记&lt;4&gt;初步控件布局
  18. (转)Maven学习总结(八)——使用Maven构建多模块项目
  19. JavaWeb应用项目部署到云ubuntu
  20. unity, ContentSizeFitter立即生效

热门文章

  1. 高级UI-符合MD的常用控件
  2. MVC之自定义过滤器(ActionFilterAttribute)
  3. mysql配置优化的参数
  4. C++中pair详解
  5. linux环境上报异常java.lang.NoSuchMethodError
  6. mysql 开启log-bin功能
  7. docker安装详细步骤-centos7
  8. [转帖]Nginx服务器的六种负载均衡策略详解
  9. [转帖]Postgresql的csv日志设置
  10. ARTS 第八周打卡