9.19 在定義的時候初始化類的成員

2018-02-24 15:27 更新

問題

你想在類被定義的時候就初始化一部分類的成員,而不是要等到實例被創(chuàng)建后。

解決方案

在類定義時就執(zhí)行初始化或設(shè)置操作是元類的一個典型應(yīng)用場景。本質(zhì)上講,一個元類會在定義時被觸發(fā),這時候你可以執(zhí)行一些額外的操作。

下面是一個例子,利用這個思路來創(chuàng)建類似于 collections 模塊中的命名元組的類:

import operator

class StructTupleMeta(type):
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for n, name in enumerate(cls._fields):
            setattr(cls, name, property(operator.itemgetter(n)))

class StructTuple(tuple, metaclass=StructTupleMeta):
    _fields = []
    def __new__(cls, *args):
        if len(args) != len(cls._fields):
            raise ValueError('{} arguments required'.format(len(cls._fields)))
        return super().__new__(cls,args)

這段代碼可以用來定義簡單的基于元組的數(shù)據(jù)結(jié)構(gòu),如下所示:

class Stock(StructTuple):
    _fields = ['name', 'shares', 'price']

class Point(StructTuple):
    _fields = ['x', 'y']

下面演示它如何工作:

>>> s = Stock('ACME', 50, 91.1)
>>> s
('ACME', 50, 91.1)
>>> s[0]
'ACME'
>>> s.name
'ACME'
>>> s.shares * s.price
4555.0
>>> s.shares = 23
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>

討論

這一小節(jié)中,類 StructTupleMeta 獲取到類屬性 _fields 中的屬性名字列表,然后將它們轉(zhuǎn)換成相應(yīng)的可訪問特定元組槽的方法。函數(shù) operator.itemgetter() 創(chuàng)建一個訪問器函數(shù),然后 property() 函數(shù)將其轉(zhuǎn)換成一個屬性。

本節(jié)最難懂的部分是知道不同的初始化步驟是什么時候發(fā)生的。StructTupleMeta 中的 __init__() 方法只在每個類被定義時被調(diào)用一次。cls 參數(shù)就是那個被定義的類。實際上,上述代碼使用了 _fields 類變量來保存新的被定義的類,然后給它再添加一點新的東西。

StructTuple 類作為一個普通的基類,供其他使用者來繼承。這個類中的 __new__() 方法用來構(gòu)造新的實例。這里使用 __new__() 并不是很常見,主要是因為我們要修改元組的調(diào)用簽名,使得我們可以像普通的實例調(diào)用那樣創(chuàng)建實例。就像下面這樣:

s = Stock('ACME', 50, 91.1) # OK
s = Stock(('ACME', 50, 91.1)) # Error

__init__() 不同的是,__new__() 方法在實例被創(chuàng)建之前被觸發(fā)。由于元組是不可修改的,所以一旦它們被創(chuàng)建了就不可能對它做任何改變。而 __init__() 會在實例創(chuàng)建的最后被觸發(fā),這樣的話我們就可以做我們想做的了。這也是為什么 __new__() 方法已經(jīng)被定義了。

盡管本節(jié)很短,還是需要你能仔細研讀,深入思考Python類是如何被定義的,實例是如何被創(chuàng)建的,還有就是元類和類的各個不同的方法究竟在什么時候被調(diào)用。

PEP 422 提供了一個解決本節(jié)問題的另外一種方法。但是,截止到我寫這本書的時候,它還沒被采納和接受。盡管如此,如果你使用的是Python 3.3或更高的版本,那么還是值得去看一下的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號