数据模型其实是对Python框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列,迭代器,函数,类和上下文管理器
一摞Python风格的纸牌
主要说明两个方法 __getitem__ 以及 __len__
Card = collections.namedtuple('Card', ['rank', 'suit'])#namedtuple,tuple的一种,不可变#名为Card,后面的rank,suit是其属性,简单来说就是一个不可变的对象包含['a', 'b']两个属性#可以通过 my_card = Card('rank', 'suit') 的方式简单的构造
classFrenchDeck:
ranks= [str(n) for n in range(2, 11)] + list('JQKA') #构造 '2 3 4...10 J Q K A'
suits = 'spades diamonds clubs hearts'.split()def __init__(self):
self._cards= [Card(rank, suit) for suit inself.suitsfor rank inself.ranks]def __len__(self):returnlen(self._cards)def __getitem__(self, item): #为了实现 obj[item] 这个操作
return self._cards[item]
首先我们构造出这个FrenchDeck类,主要是包含有两个方法 __getitem__ 以及 __len__
当一个类实现了__getitem__方法时,便可以使用obj[index] 这种类似列表的方式去访问其元素,而 __len__ 方法是为了能让 len() 函数用作
简单来说,如果你在代码里用了obj[index] 会访问到 __getitem__ 方法,另一个同理
deck =FrenchDeck()print(len(deck)) #52
print(deck[0]) #Card(rank='2', suit='spades')
print(choice(deck)) #choice 从列表中随机访问 Card(rank='6', suit='diamonds')
符合预期值,其实一开始我们可能会有点不习惯,为什么要用len,而不是用 .length() 或者 .size() 这种方法来获取长度。Python采用这种方式其实也有其好处,假象一下,我们在Java中获取长度,有时可能不知道对方是采用了length() 方法还是size()方法,亦或者其他名称来获取长度,并没有统一的规范。
ranks列表的构造是采用了列表推导的方式构造了,这个下一节会说明(我刚看时看的也是很懵,不过通过结果倒推其实还是很容易理解的)
迭代通常是隐式的,譬如说一个集合类型没有实现__contains__方法,那么in运算符就会按顺序做一次迭代搜索。于是in运算符就可以用在我们的FrenchDeck类上:
print(Card('Q', 'hearts') in deck) #True
print(Card('Q', 'beasts') in deck) #False
那么怎么排序呢?我们就按照2,3,4.....K,A 的顺序,再加上花色,黑桃最大,红桃次之,方块再次之,梅花最小。
suit_values = dict(spades = 3, hearts = 2, diamonds = 1, clubs =0)defspades_high(card):
rank_value=FrenchDeck.ranks.index(card.rank)return rank_value * len(suit_values) +suit_values[card.suit]#这个用来排序的函数有些复杂#sorted的里的key可以是函数,也可以是lambda,我们可以把函数抽象成一个lambda#首先要明白这个函数是作用是什么,给定的参数是card换句话说,需要排序的列表里的参数得有card#然后通过这个card做些许操作,得到一个值,通过这个值来比较#FrenchDeck.ranks.index(card.rank) 这行其实是获取了FrenchDeck里的ranks,然后通过传入的card来判断,传入的card应该在哪个位置#举个例子,传入了Card(rank='J', suit='diamonds'),那么rank_value得到的答案就是'J'所在的下角标,也就是9#然后返回的是 9 * 4(因为有四种花色) + 这种花色的权值#这个排序的结果就是 (clubs, 2) ,(diamonds, 2) ... (spades, A)
for card in sorted(deck, key=spades_high):print(card)###结果#Card(rank='2', suit='clubs')#Card(rank='2', suit='diamonds')#Card(rank='2', suit='hearts')#Card(rank='2', suit='spades')#...#Card(rank='A', suit='diamonds')#Card(rank='A', suit='hearts')#Card(rank='A', suit='spades')
一些特殊方法
一些特殊方法是为了被Python解释器调用的,而自己并非需要调用这些。就像上面的那个长度,我们是通过len() 来获取,而非直接 .__len__()。
如果是Python的内置类型,比方说list,str等 CPython会抄近路,直接调用其ob_size属性,因为这个读取是比读函数快的。
很多时候这些特殊方法的调用是隐式的,比方说for i in x 这个语句,其实是用了iter(x) 方法,背后则是 x.__iter__()。这时你可能就会好奇了,我们的FrenchDeck其实并没有实现iter方法,却可以迭代,这是为什么。其实主要是__getitem__方法的功劳,关于迭代的具体流程之后还会说明。
还有不要想当然的去添加一些特殊方法,现在没有的,以后说不定会有
模拟数值类型
利用特殊方法,可以让自定义对象通过加号‘+’进行运算,简单来说,可以变相实现像C++那种重载运算符
classVector:def __init__(self, x = 0, y =0):
self.x=x
self.y=ydef __repr__(self): #类似于java里的 toString
return 'Vector(%r, %r)' %(self.x, self.y)def __abs__(self): #获取绝对值,可以通过abs() 来访问
return hypot(self.x, self.y) #通俗来说,就是获取以a, b为边的直角三角形斜边,放这里的意思就是向量长
def __bool__(self):returnbool(abs(self))def __add__(self, other): #可以用 + 来计算两个向量相加
x = self.x +other.x
y= self.y +other.yreturnVector(x, y)def __mul__(self, scalar): #可以和数字相乘
return Vector(self.x * scalar, self.y * scalar)
关于__repr__方法再多说两句,__repr__几乎等价于Java中的toString()函数(友情提示你一下,Java里你即使实现了toString方法,也不能用String强转)。
__str__ 函数的用途是在调用 str(obj)的时候访问的,但是print(obj) 是不会调用__str__ 方法的。不论你有没有实现__repr__
但是反过来说,如果你没有实现__str__ 函数,但是有__repr__函数,那么解释器会帮你调用__repr__方法。
特殊方法一览
和运算符无关的特殊方法
类别
方法名
字符串/字节序列表示形式
__repr__, __str__, __format__, __bytes__
数值转换
__abs__, __bool__, __complex__, __int__, __float__, __hash__, __index__
集合模拟
__len__, __getitem__, __setitem__, __delitem__, __contains__
迭代枚举
__iter__, __reversed__, __next__
可调用模拟
__call__
上下文管理
__enter__, __exit__
实例创建和销毁
__new__, __init__, __del__
属性管理
__getattr__, __setattr__, getattribute__, __setattribute__, __delattr__, __dir__
属性描述符
__get__, __set__, __delete__
跟类相关的服务
__prepare__, __instancecheck__, __subclasscheck__
和运算符相关的特殊方法
类别
方法名和对应的运算符
一元运算符
__neg__ - , __pos__ +, __abs__ abs()
众多比较运算符
__lt__ , __ge__ >=
算术运算符
__add__ +, __sub__ - , __mul__ *, __truediv__ /, __floordiv //, __mod__ %, __divmod__ divmod(), __pow__ **或pow(), __round__ round()
反向算术运算符
__radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__
增量赋值算术运算符
__iadd__, __isub__, __imul__ , __itruediv__, __ifloordiv__, __imod__, __ipow__
位运算符
__invert__ ~, __lshift__ <>, __and__ &, __or__ |, __xor__ ^
反向位运算符
__rlshift__, __rrshift__, __rand__, __rxor__, __ror__
增量赋值位运算符
__ilshift__, __irshift__, __iand__, __ixor__, __ior__
杂谈(非正式向)
这个表是真的难操控,姑且就这样吧
老实说,我最开始看这一节花了很多时间,这第一节确实有些硬核,用到了很多之前见都没见过的方法或者方式。
有的时候正着不行就反着来,从结果出发倒推代码逻辑,然后去理解里面的用意能帮助我们不少。