1. 程式人生 > >Python數據模型及Pythonic編程

Python數據模型及Pythonic編程

exc .com from 繼承 構建 ffffff 依然 實例 class

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 模式。下面是一個示例。

"""
LookingGlass對象的上下文管理,進入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

Python數據模型及Pythonic編程