簡體   English   中英

在 Python 中創建對象時設置類屬性的最佳方法是什么?

[英]What is the best way to set the attributes of a class at object creation time in Python?

當我定義一個類時,我經常想在創建對象時為該類設置一組屬性。 到目前為止,我是通過將屬性作為參數傳遞給init方法來實現的。 但是,我對此類代碼的重復性感到不滿:

class Repository(OrderedDict,UserOwnedObject,Describable):
  def __init__(self,user,name,gitOriginURI=None,gitCommitHash=None,temporary=False,sourceDir=None):
    self.name = name
    self.gitOriginURI = gitOriginURI
    self.gitCommitHash = gitCommitHash
    self.temporary = temporary
    self.sourceDir = sourceDir
    ...

在這個例子中,我必須輸入 3 次name 3 次gitOriginURI 3 次gitCommitHash 3 次temporary和 3 次sourceDir 只是為了設置這些屬性。 這是非常無聊的代碼編寫。

我考慮過將這樣的類更改為:

class Foo():
  def __init__(self):
    self.a = None
    self.b = None
    self.c = None

並初始化他們的對象,如:

f = Foo()
f.a = whatever
f.b = something_else
f.c = cheese

但是從文檔的角度來看,這似乎更糟,因為類的用戶需要知道需要設置哪些屬性,而不是簡單地查看類初始化程序的自動生成的help()字符串。

有沒有更好的方法來做到這一點?

我認為可能是一個有趣的解決方案的一件事是,如果有一個store_args_to_self()方法,它將存儲傳遞給 init 的每個參數作為 self 的屬性。 有這樣的方法嗎?

讓我對這種尋求更好方法感到悲觀的一件事是,例如查看 cPython 源代碼中date對象的源代碼,我看到了相同的重復代碼:

def __new__(cls, year, month=None, day=None):
    ...
    self._year = year
    self._month = month
    self._day = day

https://github.com/python/cpython/blob/master/Lib/datetime.py#L705

而 urwid,雖然被 setter 的使用稍微混淆了,但也有這樣的“接受一個參數並將其設置為 self 的一個屬性”的熱土豆代碼:

def __init__(self, caption=u"", edit_text=u"", multiline=False,
        align=LEFT, wrap=SPACE, allow_tab=False,
        edit_pos=None, layout=None, mask=None):
    ...

    self.__super.__init__("", align, wrap, layout)
    self.multiline = multiline
    self.allow_tab = allow_tab
    self._edit_pos = 0
    self.set_caption(caption)
    self.set_edit_text(edit_text)
    if edit_pos is None:
        edit_pos = len(edit_text)
    self.set_edit_pos(edit_pos)
    self.set_mask(mask)

https://github.com/urwid/urwid/blob/master/urwid/widget.py#L1158

您可以使用dataclasses項目讓它負責為您生成__init__方法; 它還將處理表示、散列和相等性測試(以及可選的豐富比較和不變性):

from dataclasses import dataclass
from typing import Optional

@dataclass
class Repository(OrderedDict, UserOwnedObject, Describable):
    name: str
    gitOriginURI: Optional[str] = None
    gitCommitHash: Optional[str] = None
    temporary: bool = False
    sourceDir: Optional[str] = None

dataclassesPEP 557 - Data Classes中定義,它已被接受包含在 Python 3.7 中。 該庫將在 Python 3.6 及更高版本上運行(因為它依賴於 3.6 中引入的新變量注釋語法)。

該項目的靈感來自attrs項目,它仍然提供了更多的靈活性和選項,以及與 Python 2.7 和 Python 3.4 及更高版本的兼容性。

好吧,你可以這樣做:

class Foo:
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

foo = Foo(a=1, b='two', c='iii')
print(foo.a, foo.b, foo.c)

輸出

1 two iii

但是如果你這樣做了,在將kwargs中的鍵轉儲到你的實例__dict__之前檢查它們是否正常可能是一個好主意。 ;)

這是一個稍微高級的示例,它對傳入的參數進行了一些檢查。

class Foo:
    attrs = ['a', 'b', 'c']
    ''' Some stuff about a, b, & c '''
    def __init__(self, **kwargs):
        valid = {key: kwargs.get(key) for key in self.attrs}
        self.__dict__.update(valid)

    def __repr__(self):
        args = ', '.join(['{}={}'.format(key, getattr(self, key)) for key in self.attrs])
        return 'Foo({})'.format(args)

foo = Foo(a=1, c='iii', d='four')
print(foo)

輸出

Foo(a=1, b=None, c=iii)

對於 Python 2.7,我的解決方案是從 namedtuple 繼承並使用 namedtuple 本身作為 init 的唯一參數。 為了避免每次我們可以使用裝飾器時都重載 new。 優點是我們有明確的init簽名,沒有 *args、**kwargs 以及很好的 IDE 建議

def nt_child(c):
    def __new__(cls, p): return super(c, cls).__new__(cls, *p)
    c.__new__ = staticmethod(__new__)
    return c

ClassA_P = namedtuple('ClassA_P', 'a, b, foo, bar')

@nt_child
class ClassA(ClassA_P):
    def __init__(self, p):
        super(ClassA, self).__init__(*p)
        self.something_more = sum(p)

a = ClassA(ClassA_P(1,2,3,4)) # a = ClassA(ClassA_P( <== suggestion a, b, foo, bar
print a.something_more # print a. <== suggesion a, b, foo, bar, something_more

我會在這里留下另一個食譜。 attrs很有用,但也有缺點,其中主要是缺乏__init__類的 IDE 建議。

擁有初始化鏈也很有趣,我們使用父類的實例作為__init__第一個參數,而不是一個一個地提供它的所有屬性。

所以我建議使用簡單的裝飾器。 它分析__init__簽名並基於它自動添加類屬性(因此方法與 attrs 的方法相反)。 這為我們提供了__init__很好的 IDE 建議(但缺乏對屬性本身的建議)。

用法:

@data_class
class A:
    def __init__(self, foo, bar): pass

@data_class
class B(A):
    # noinspection PyMissingConstructor
    def __init__(self, a, red, fox):
        self.red_plus_fox = red + fox
        # do not call parent constructor, decorator will do it for you

a = A(1, 2)
print a.__attrs__ # {'foo': 1, 'bar': 2}

b = B(a, 3, 4) # {'fox': 4, 'foo': 1, 'bar': 2, 'red': 3, 'red_plus_fox': 7}
print b.__attrs__

來源:

from collections import OrderedDict

def make_call_dict(f, is_class_method, *args, **kwargs):
    vnames = f.__code__.co_varnames[int(is_class_method):f.__code__.co_argcount]
    defs = f.__defaults__ or []

    d = OrderedDict(zip(vnames, [None] * len(vnames)))
    d.update({vn: d for vn, d in zip(vnames[-len(defs):], defs)})
    d.update(kwargs)
    d.update({vn: v for vn, v in zip(vnames, args)})
    return d

def data_class(cls):
    inherited = hasattr(cls, '_fields')
    if not inherited: setattr(cls, '_fields', None)
    __init__old__ = cls.__init__

    def __init__(self, *args, **kwargs):
        d = make_call_dict(__init__old__, True, *args, **kwargs)

        if inherited:
            # tricky call of parent __init__
            O = cls.__bases__[0]  # put parent dataclass first in inheritance list
            o = d.values()[0]  # first arg in my __init__ is parent class object
            d = OrderedDict(d.items()[1:])
            isg = o._fields[O]  # parent __init__ signature, [0] shows is he expect data object as first arg
            O.__init__(self, *(([o] if isg[0] else []) + [getattr(o, f) for f in isg[1:]]))
        else:
            self._fields = {}

        self.__dict__.update(d)

        self._fields.update({cls: [inherited] + d.keys()})

        __init__old__(self, *args, **kwargs)

    cls.__attrs__ = property(lambda self: {k: v for k, v in self.__dict__.items()
                                           if not k.startswith('_')})
    cls.__init__ = __init__
    return cls

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM