Python做爲一種多範式語言,它的不少語言特性都能從其餘語言上找到參照,可是Python依然造成了一套本身的「Python 風格」(Pythonic)。這種Pythonic風格徹底體如今 Python 的數據模型上,而數據模型中的元接口(指那些名字以兩個下劃線開頭,以兩個下劃線結尾的特殊方法,例如 __getitem__),就是編寫地道的Python代碼的祕密所在。這種基於元接口實現的設計模式,也叫鴨子類型(duck typing)。html
鴨子類型指的是對象的類型可有可無,只要實現了特定的接口便可。忽略對象的真正類型,轉而關注對象有沒有實現所需的方法、簽名和語義。Python的數據模型都支持鴨子類型,鴨子類型也是地道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__ 方法,那麼它的實例能夠做爲函數調用。示例以下。ide
""" >>> 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 異常。ui
迭代器能夠迭代,可是可迭代的對象不是迭代器,也必定不能是自身的迭代器。也就是說,可迭代的對象必須實現 __iter__ 方法,但不能實現 __next__ 方法。spa
只要實現__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