8.11 簡(jiǎn)化數(shù)據(jù)結(jié)構(gòu)的初始化

2018-02-24 15:26 更新

問(wèn)題

你寫了很多僅僅用作數(shù)據(jù)結(jié)構(gòu)的類,不想寫太多煩人的 __init__() 函數(shù)

解決方案

可以在一個(gè)基類中寫一個(gè)公用的 __init__() 函數(shù):

import math

class Structure1:
    # Class variable that specifies expected fields
    _fields = []

    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))
        # Set the arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

然后使你的類繼承自這個(gè)基類:

# Example class definitions
class Stock(Structure1):
    _fields = ['name', 'shares', 'price']

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

class Circle(Structure1):
    _fields = ['radius']

    def area(self):
        return math.pi * self.radius ** 2

使用這些類的示例:

>>> s = Stock('ACME', 50, 91.1)
>>> p = Point(2, 3)
>>> c = Circle(4.5)
>>> s2 = Stock('ACME', 50)
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "structure.py", line 6, in __init__
        raise TypeError('Expected {} arguments'.format(len(self._fields)))
TypeError: Expected 3 arguments

如果還想支持關(guān)鍵字參數(shù),可以將關(guān)鍵字參數(shù)設(shè)置為實(shí)例屬性:

class Structure2:
    _fields = []

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        # Set all of the positional arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        # Set the remaining keyword arguments
        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))

        # Check for any remaining unknown arguments
        if kwargs:
            raise TypeError('Invalid argument(s): {}'.format(','.join(kwargs)))
# Example use
if __name__ == '__main__':
    class Stock(Structure2):
        _fields = ['name', 'shares', 'price']

    s1 = Stock('ACME', 50, 91.1)
    s2 = Stock('ACME', 50, price=91.1)
    s3 = Stock('ACME', shares=50, price=91.1)
    # s3 = Stock('ACME', shares=50, price=91.1, aa=1)

你還能將不在 _fields 中的名稱加入到屬性中去:

class Structure3:
    # Class variable that specifies expected fields
    _fields = []

    def __init__(self, *args, **kwargs):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        # Set the arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        # Set the additional arguments (if any)
        extra_args = kwargs.keys() - self._fields
        for name in extra_args:
            setattr(self, name, kwargs.pop(name))

        if kwargs:
            raise TypeError('Duplicate values for {}'.format(','.join(kwargs)))

# Example use
if __name__ == '__main__':
    class Stock(Structure3):
        _fields = ['name', 'shares', 'price']

    s1 = Stock('ACME', 50, 91.1)
    s2 = Stock('ACME', 50, 91.1, date='8/2/2012')

討論

當(dāng)你需要使用大量很小的數(shù)據(jù)結(jié)構(gòu)類的時(shí)候,相比手工一個(gè)個(gè)定義 __init__() 方法而已,使用這種方式可以大大簡(jiǎn)化代碼。

在上面的實(shí)現(xiàn)中我們使用了 setattr() 函數(shù)類設(shè)置屬性值,你可能不想用這種方式,而是想直接更新實(shí)例字典,就像下面這樣:

class Structure:
    # Class variable that specifies expected fields
    _fields= []
    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        # Set the arguments (alternate)
        self.__dict__.update(zip(self._fields,args))

盡管這也可以正常工作,但是當(dāng)定義子類的時(shí)候問(wèn)題就來(lái)了。當(dāng)一個(gè)子類定義了 __slots__ 或者通過(guò)property(或描述器)來(lái)包裝某個(gè)屬性,那么直接訪問(wèn)實(shí)例字典就不起作用了。我們上面使用 setattr() 會(huì)顯得更通用些,因?yàn)樗策m用于子類情況。

這種方法唯一不好的地方就是對(duì)某些IDE而已,在顯示幫助函數(shù)時(shí)可能不太友好。比如:

>>> help(Stock)
Help on class Stock in module __main__:
class Stock(Structure)
...
| Methods inherited from Structure:
|
| __init__(self, *args, **kwargs)
|
...
>>>

可以參考9.16小節(jié)來(lái)強(qiáng)制在 __init__() 方法中指定參數(shù)的類型簽名。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)