第一章 python数据模型

1.1 通过一段python风格的扑克牌代码来理解

1、理解双下滑线的函数__init__和__len__等

2、理解 继承。python2中需要显示写FrenchDeck(object),python3中继承关系是默认的。可以直接用FrenchDeck.属性。

import collections

# 定义一张纸牌,'数值','花色'。利用nametuple可以得到一个纸牌对象
Card = collections.namedtuple('Card',['rank','suit'])


class FrenchDeck:
    ranks = [str(i) for i in range(2,11)]+list('JQKA')
    suits = 'spades diamonds clubs hearts'.split(' ')

    def __init__(self):
        self._cards = [Card(r, s) for s in self.suits for r in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

    def __setitem__(self, key, value):  # 可以定义洗牌函数来实现洗牌功能。
        pass


Card_all = FrenchDeck()   # 实现一个完整的扑克牌
print(len(Card_all))      # 这是因为__len__
print(Card_all[0],Card_all[-1])   # 这是由__getitem__得到的

beer_Card = Card('7','spades')    # 定义某一个牌的数字以及花色  得到一个纸牌对象
print(beer_Card)

from random import choice
print(choice(Card_all))      # 对于随机抽取一张牌,不需要单独写一个方法了,因为已经有内置函数choice()可以用

# 仅仅通过__getitem__即可实现牌的迭代
# for i in Card_all:  # 以及反向迭代reversed(Card_all)
#     print(i)

# 对这摞牌排序。虽然Card_all集成object类,但是对于排序需要额外的函数实现排序功能。
def spades_high(card):
    suit_value = dict(spades=3, diamonds=2, clubs=1, hearts=0)
    rank_value = FrenchDeck.ranks.index(card.rank)
    return rank_value*len(suit_value)+suit_value[card.suit]

for i in sorted(Card_all,key=spades_high):
    print(i)

1.2 如何使用特殊方法

特殊方法的存在是为了被 Python 解释器调用的,没有 my_object.len() 这种写法,而应该使用 len(my_object)。在执行 len(my_object) 的时候,如果 my_object 是一个自定义类的对象,那么 Python 会自己去调用其中由你实现的 len 方法。

通常你的代码无需直接使用特殊方法。除非有大量的元编程存在。唯一的例外可能是 init 方法,常用到,为了自己的子类的 init 方法中调用超类的构造器。

1.2.1 模拟数值类型

一个例子 : Vector(2,4) + Vextor(2,1) = Vector(4,5)

ps:Python 内置的 complex 类可以用来表示二维向量,但我们这个自定义的类Vector可以扩展到 n 维向量。
在这里插入图片描述
abs 是一个内置函数,如果输入是整数或者浮点数,它返回的是输入值的绝对值;如果输入是复数(complex number),那么返回这个复数的模。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
上述例子包含了一个 Vector 类的实现,上面提到的操作在代码里是用这些特殊方法实现
的:reprabsaddmul

import math

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 __str__(self):
       # return 'Vector(%r,%r)' % (self.x,self.y)
    # def __str__(self):
     # return 'Vector({x},{y})'.format(x = self.x,y = self.y)

    def __abs__(self):
        return math.hypot(self.x,self.y)    # hypot() 返回欧几里德范数 sqrt(x*x + y*y)。

    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, other):
        x = self.x*other
        y = self.y*other
        return Vector(x,y)


v1 = Vector(3,4)
v2 = Vector(2,1)

print(v1+v2)   # Vector(5,5)
print(v1*2)    # Vector(6,8)
print(abs(v1)) # 5.0

虽然代码里有 6 个特殊方法,但这些方法(除了 init)并不会在这个类自身的代码中使用。即便其他程序要使用这个类的这些方法,也不会直接调用它们,就像我们在上面的控制台对话中看到的。上文也提到过,一般只有 Python 的解释器会频繁地直接调用这些方法。接下来看看每个特殊方法的实现。

1.2.2 字符串表示形式(函数__repr__)

上节__repr__这个方法是用来得到一个对象的字符串的形式。如果没有实现 repr,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是 <Vector object at 0x10e100070>。
也暗示了一个关键:Vector(1, 2) 和 Vector(‘1’, ‘2’) 是不一样。

同样,str.format 函数所用到的新式字符串格式化语法也是利用了 repr,才把 !r 字段变成字符串。

reprstr 的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
如果你只想实现这两个特殊方法中的一个,repr 是更好的选择,因为如果一个对象没有 str 函数,而 Python 又需要调用它的时候,解释器会用 repr 作为替代。

1.2.3 算术运算符(函数__add__和__mul__)

通过 addmul,示例 1-2 为向量类带来了 + 和 * 这两个算术运算符。中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值。

1.2.4 自定义的布尔值(函数__bool__)

我们对__bool__ 的实现很简单,如果一个向量的模是 0,那么就返回 False,其他情况则返回 True。因为__bool__ 函数的返回类型应该是布尔型,所以我们通过bool(abs(self)) 把模值变成了布尔值。
在这里插入图片描述

1.3 特殊方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当交换两个操作数的位置时,就会调用反向运算符(b * a 而不是 a * b)。增量赋值运算符则是一种把中缀运算符变成赋值运算的捷径(a = a * b 就变成了 a*= b)。

1.4 为什么len不是普通方法

如果 x 是一个内置类型的实例,那么 len(x) 的速度会非常快。CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法。获取一个集合中元素的数量是一个很常见的操作,在 str、list、memoryview等类型上,这个操作必须高效。

len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理。

1.5 本章小结

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。

Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过__repr__和__str__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的。这就是数据模型中存在特殊方法__repr__ 和__str__ 的原因。

对序列数据类型的模拟是特殊方法用得最多的地方,这一点在 FrenchDeck 类的示例中有所展现。

Python 通过运算符重载这一模式提供了丰富的数值类型,除了内置的那些之外,还有decimal.Decimal 和 fractions.Fraction。这些数据类型都支持中缀算术运算符。在第 13 章中,我们还会通过对 Vector 类的扩展来学习如何实现这些运算符,当然还会提到如何让运算符满足交换律增强赋值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值