9.19 在定義的時(shí)候初始化類的成員

2018-02-24 15:27 更新

問(wèn)題

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

解決方案

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

下面是一個(gè)例子,利用這個(gè)思路來(lái)創(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)

這段代碼可以用來(lái)定義簡(jiǎn)單的基于元組的數(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)的可訪問(wèn)特定元組槽的方法。函數(shù) operator.itemgetter() 創(chuàng)建一個(gè)訪問(wèn)器函數(shù),然后 property() 函數(shù)將其轉(zhuǎn)換成一個(gè)屬性。

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

StructTuple 類作為一個(gè)普通的基類,供其他使用者來(lái)繼承。這個(gè)類中的 __new__() 方法用來(lái)構(gòu)造新的實(shí)例。這里使用 __new__() 并不是很常見(jiàn),主要是因?yàn)槲覀円薷脑M的調(diào)用簽名,使得我們可以像普通的實(shí)例調(diào)用那樣創(chuàng)建實(shí)例。就像下面這樣:

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

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

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

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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)