《Fluent Python》- 01 Python数据模型

数据模型其实是对Python框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括但不限于序列,迭代器,函数,类和上下文管理器

一摞Python风格的纸牌

主要说明两个方法   __getitem__  以及  __len__

 

Card = collections.namedtuple('Card', ['rank', 'suit'])
# namedtuple,tuple的一种,不可变
# 名为Card,后面的rank,suit是其属性,简单来说就是一个不可变的对象包含['a', 'b']两个属性
# 可以通过 my_card = Card('rank', 'suit') 的方式简单的构造
class FrenchDeck:
    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 in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(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)
def spades_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++那种重载运算符

 

class Vector:
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y

    def __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):
        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__方法再多说两句,__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__  <,  __le__  <=,  __eq__  =,  __ne__   !=,  __gt__  >, __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__ <<,  __rshift__ >>,  __and__ &, __or__ |, __xor__ ^
反向位运算符 __rlshift__,  __rrshift__, __rand__, __rxor__, __ror__
增量赋值位运算符 __ilshift__,  __irshift__, __iand__, __ixor__, __ior__

杂谈(非正式向)

这个表是真的难操控,姑且就这样吧

老实说,我最开始看这一节花了很多时间,这第一节确实有些硬核,用到了很多之前见都没见过的方法或者方式。

有的时候正着不行就反着来,从结果出发倒推代码逻辑,然后去理解里面的用意能帮助我们不少。

 

上一篇:轮播图实现


下一篇:swiper遇到的问题