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')