第一章 Python数据模型

Guido 对语言设计美学的理解令人惊叹。我认识不少优秀的语言设计师,他们可以构建出理论上看起来漂亮的语言,但没人会使用。Guido知道如何在理论上做出一定妥协,设计出来的语言让使用者如沐春风,这真是不可多得。

                ------Jim Hugunin, creator of Jython, cocreator of AspectJ, and architect of the .Net DLR

Python 的最佳品质之一是它的一致性。在使用 Python 一段时间后,你就会开始理解python语言,并能正确的猜测出全新的语言特征的作用。

但是,如果在接触 Python 前学习了另一种面向对象语言,你可能会发现使用 len(collection) 而不是 collection.len()的用法很奇怪。这种明显怪现象只是冰山一角,但是如果正确理解python语言后,你会发现这就是我们称之为 Pythonic 的关键之处。这座冰山被称为 Python 数据模型,我们平时自己创建对象就要使用数据模型的API,确保使用最地道的语言功能。

数据模型其实是对python框架的描述。数据模型规范了语言自身各个组成部分的接口,例如序列、函数、迭代器、协程、类、上下文管理器等。

在使用框架时,我们会花费大量时间实现方法来交给框架调用。当我们用 Python 数据模型构建新的类时也是一样的情况。Python 解释器调用特殊方法来执行基本的对象操作,这些操作通常由特殊语法触发。特殊的方法名称以两个下划线开头和两个下划线结尾。例如,__getitem__是支持语法 obj[key]的 特殊方法。为了获得 my_collection[key]的值,解释器调用 my_collection.__getitem__(key)。

这些特殊方法名能让你自己的对象实现和支持以下的语法架构,并与之交互。

  • 集合
  • 属性访问
  • 迭代(包括使用async for的异步迭代)
  • 运算符重载
  • 函数和方法的调用
  • 字符串的展示和格式化
  • 使用关键字await的异步编程
  • 对象的创建和销毁
  • 管理器上下文(包括使用async with的异步上下文管理器)

MAGIC AND DUNDER

术语魔术方法是特殊方法的别名,但是我们如何表示像__getitem__这样的特殊方法?我从作家兼老师史蒂夫霍尔顿那里学会了说“dunder-getitem”。“Dunder”是“前后双下划线”的缩写。这就是为什么特殊方法也被称为dunder方法的原因。The Python Language Reference 的“Lexical Analysis” 一章警告说,“在任何上下文中,不遵循官方文档使用 __*__ 命名的特殊方法,都会带来麻烦“

本章更新的内容

本章与第一版相比几乎没有变化,因为它是对 Python 数据模型的介绍并且Python 数据模型非常稳定。最显着的变化是:

  • 支持异步编程和其他新特性的特殊方法,已经添加到表‘Overview of Special Methods’中
  • 图1-2展示了包括“Collection API”中特殊方法的使用,包括 Python 3.6 中引入的 collections.abc.Collection 抽象基类

在整个第二版中,我都采用了 Python 3.6 中引入的 f-string 语法,它比旧的字符串格式符号(str.format() 方法和 % 运算符)更具可读性,而且通常更方便。

TIP

仍然使用 my_fmt.format() 的一个原因是有的时候 my_fmt 的定义是在代码中不经常进行格式化操作的位置。例如,当 my_fmt 有多行并且在常量中定义时,或者当它需要读取配置文件或数据库。这些是真实需求,但不会经常发生。

一摞Python风格的卡牌

示例 1-1 很简单,但它展示了仅实现两个特殊方法 __getitem__ 和 __len__ 就具有的强大功能。

示例 1-1。扑克牌序列

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    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, position):
        return self._cards[position]

首先要注意的是使用 collections.namedtuple 构造一个简单的类来表示单张卡牌。 我们使用 namedtuple 来构建卡牌对象的类,这些类通常只有几个属性,而且没有自定义方法,例如数据库记录。在示例中,我们使用它为扑克牌中的卡牌提供一个友好的表示,如控制台会话所示:

>>> beer_card = Card('7', 'diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')

但是这个例子的重点是 FrenchDeck 类。它短小又精悍。首先,与任何标准 Python 集合一样,他跟任何标准python集合类型一样,可以用len函数查看一叠牌中卡牌的数量。

>>> deck = FrenchDeck()
>>> len(deck)
52

由于 __getitem__ 方法,从牌组中读取特定卡牌(例如第一张或最后一张)很容易: 

>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')

我们应该创建一种方法来随机抽取卡牌吗?不需要。 Python 已经内置一个从序列中获取随机项的函数:random.choice。我们可以在直接在Deck实例上使用: 

>>> from random import choice
>>> choice(deck)
Card(rank='3', suit='hearts')
>>> choice(deck)
Card(rank='K', suit='spades')
>>> choice(deck)
Card(rank='2', suit='clubs')

我们刚刚看到了使用特

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值