Python数据模型及Pythonic编程
Python作为一种多范式语言,它的很多语言特性都能从其他语言上找到参照,但是Python依然形成了一套自己的“Python 风格”(Pythonic)。这种Pythonic风格完全体现在 Python 的数据模型上,而数据模型中的元接口(指那些名字以两个下划线开头,以两个下划线结尾的特殊方法,例如 __getitem__),就是编写地道的Python代码的秘密所在。这种基于元接口实现的设计模式,也叫鸭子类型(duck typing)。
鸭子类型指的是对象的类型无关紧要,只要实现了特定的接口即可。忽略对象的真正类型,转而关注对象有没有实现所需的方法、签名和语义。Python的数据模型都支持鸭子类型,鸭子类型也是地道Python编程鼓励的风格,所以如果觉得自己想创建新的抽象基类,先试着通过常规的鸭子类型来解决问题。
数据模型其实是对 Python 框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括类、函数、序列、迭代器、上下文管理器等。
类
得益于 Python 数据模型,自定义类的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是元接口。Python给类设计了大量的元接口,具体请参看Python 语言参考手册中的“Data Model”章节。下面是一些类的元接口的展示。
- """
- >>> v1 = Vector2d(3, 4)
- 通过元接口__iter__支持拆包
- >>> x, y = v1
- >>> x, y
- (3.0, 4.0)
- 通过元接口__repr__支持字面量表示和repr函数
- >>> v1
- Vector2d(3.0, 4.0)
- >>> v1_clone = eval(repr(v1))
- >>> v1 == v1_clone
- True
- 通过元接口__str__支持print函数
- >>> print(v1)
- (3.0, 4.0)
- 通过元接口__bytes__支持bytes函数
- >>> octets = bytes(v1)
- >>> octets
- b\'d\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@\'
- 通过元接口__abs__支持abs函数
- >>> abs(v1)
- 5.0
- 通过元接口__bool__支持bool函数
- >>> bool(v1), bool(Vector2d(0, 0))
- (True, False)
- 通过property支持可读属性
- >>> v1.x, v1.y
- (3.0, 4.0)
- >>> v1.x = 123
- Traceback (most recent call last):
- ...
- AttributeError: can\'t set attribute
- 通过__hash__支持对象可散列,支持dict、set等函数
- >>> hash(v1)
- 7
- >>> set(v1)
- {3.0, 4.0}
- >>> {v1: \'point1\'}
- {Vector2d(3.0, 4.0): \'point1\'}
- """
- from array import array
- import math
- class Vector2d:
- typecode = \'d\'
- def __init__(self, x, y):
- self.__x = float(x)
- self.__y = float(y)
- @property
- def x(self):
- return self.__x
- @property
- def y(self):
- return self.__y
- def __iter__(self):
- return (i for i in (self.x, self.y))
- def __repr__(self):
- class_name = type(self).__name__
- return \'{}({!r}, {!r})\'.format(class_name, *self)
- def __str__(self):
- return str(tuple(self))
- def __bytes__(self):
- return (bytes([ord(self.typecode)]) +
- bytes(array(self.typecode, self)))
- def __eq__(self, other):
- return tuple(self) == tuple(other)
- def __hash__(self):
- return hash(self.x) ^ hash(self.y)
- def __abs__(self):
- return math.hypot(self.x, self.y)
- def __bool__(self):
- return bool(abs(self))
函数
Python中一切皆对象,函数也不例外,而且Python中的函数还是一等对象。函数可以理解为一种可调用对象语法糖。
可调用对象的元接口是__call__。如果一个类定义了 __call__ 方法,那么它的实例可以作为函数调用。示例如下。
- """
- >>> pickcard = Cards(range(52))
- >>> pickcard()
- 51
- >>> pickcard()
- 50
- >>> callable(pickcard)
- True
- """
- class Cards:
- def __init__(self, items):
- self._items = list(items)
- def __call__(self):
- return self._items.pop()
序列
Python 的序列数据模型的元接口很多,但是对象只需要实现 __len__ 和 __getitem__ 两个方法,就能用在绝大部分期待序列的地方,如迭代,[]运算符、切片、for i in 等操作。示例如下:
- """
- >>> poker = Poker()
- 支持len运算
- >>> len(poker)
- 52
- 支持[]运算
- >>> poker[0]
- Card(rank=\'2\', suit=\'spades\')
- >>> poker[-1]
- Card(rank=\'A\', suit=\'hearts\')
- 支持切片运算
- >>> poker[12::13]
- [Card(rank=\'A\', suit=\'spades\'), Card(rank=\'A\', suit=\'diamonds\'), Card(rank=\'A\', suit=\'clubs\'), Card(rank=\'A\', suit=\'hearts\')]
- 支持 for i in 运算
- >>> for card in poker: print(card) # doctest: +ELLIPSIS
- ...
- Card(rank=\'2\', suit=\'spades\')
- Card(rank=\'3\', suit=\'spades\')
- Card(rank=\'4\', suit=\'spades\')
- ...
- 支持 in 运算
- >>> Card(\'7\', \'hearts\') in poker
- True
- """
- import collections
- Card = collections.namedtuple(\'Card\', [\'rank\', \'suit\'])
- class Poker:
- 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]
从测试用例上可以看出它具有序列所有特性,即便它是 object 的子类也无妨。因为它的行为像序列,那我们就可以说它是序列。
迭代
Python中,可迭代对象的元接口是__iter__。迭代器可以从可迭代的对象中获取,__iter__和__next__是它的2个主要的元接口。__iter__ 方法使对象支持迭代,__next__ 方法返回序列中的下一个元素。如果没有元素了,那么抛出 StopIteration 异常。
迭代器可以迭代,但是可迭代的对象不是迭代器,也一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__ 方法,但不能实现 __next__ 方法。
只要实现__iter__接口的对象,就是迭代鸭子类型,自然就支持所有的迭代运算。示例如下:
- """
- >>> s = Sentence(\'hello world\')
- >>> s
- Sentence(\'hello world\')
- 支持迭代list运算
- >>> list(s)
- [\'hello\', \'world\']
- 获取迭代器
- >>> it = iter(s)
- 支持迭代器next运算
- >>> next(it)
- \'hello\'
- >>> next(it)
- \'world\'
- >>> next(it)
- Traceback (most recent call last):
- ...
- StopIteration
- 支持迭代for运算
- >>> for w in s: print(w)
- hello
- world
- """
- import re
- import reprlib
- RE_WORD = re.compile(\'\w+\')
- class Sentence:
- def __init__(self, text):
- self.text = text
- def __repr__(self):
- return \'Sentence(%s)\' % reprlib.repr(self.text)
- def __iter__(self):
- word_iter = RE_WORD.finditer(self.text)
- return SentenceIter(word_iter)
- class SentenceIter():
- def __init__(self, word_iter):
- self.word_iter = word_iter
- def __next__(self):
- match = next(self.word_iter)
- return match.group()
- def __iter__(self):
- return self
上面这个例子中,可迭代对象Sentence通过定义迭代器SentenceIter的方式实现。更Pythonic的做法是通过生成器yield来实现。下面是一个示例,能通过上面的所有测试用例,但代码更加精简。
- RE_WORD = re.compile(\'\w+\')
- class Sentence:
- def __init__(self, text):
- self.text = text
- def __repr__(self):
- return \'Sentence(%s)\' % reprlib.repr(self.text)
- def __iter__(self):
- for match in RE_WORD.finditer(self.text):
- yield match.group()
上下文管理器
Python的with关键字是上下文管理器语法糖,上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮演 finally 子句的角色。可以看出,上下文管理器简化了 try/finally 模式。下面是一个示例。
- """
- ReversePrint对象的上下文管理,进入with块后,标准输出反序打印,
- 退出with块后,标准输出恢复正常状态。
- >>> with ReversePrint() as what:
- ... print(\'Hello world!\')
- !dlrow olleH
- >>> print(\'Hello world!\')
- Hello world!
- """
- class ReversePrint:
- def __enter__(self):
- import sys
- self.original_write = sys.stdout.write
- sys.stdout.write = self.reverse_write
- return \'JABBERWOCKY\'
- def reverse_write(self, text):
- self.original_write(text[::-1])
- def __exit__(self, exc_type, exc_value, traceback):
- import sys
- sys.stdout.write = self.original_write
- if exc_type is ZeroDivisionError:
- print(\'Please DO NOT divide by zero!\')
- return True