簡體   English   中英

具有可變和不可變屬性的數據類樣式 object?

[英]Dataclass-style object with mutable and immutable properties?

我一直在使用從文件中動態加載屬性名稱的數據類,但我無法找到一種方法來創建“凍結”和“非凍結”屬性。 我相信數據類只允許您將所有屬性設置為凍結或非凍結。

到目前為止,我創建了一個凍結的數據類並添加了一個可變的 class 作為我可以更改為 go 的屬性之一,但我對這種方法的可讀性不太滿意。

人們是否會推薦另一個 pythonic 數據類,而無需實現具有設置可變/不可變屬性的能力的 class?

import dataclasses

class ModifiableConfig:
    """There is stuff in here but you get the picture."""
    ...

config_dataclass = dataclasses.make_dataclass(
    'c',
    [(x, type(x), v) for x, v in config.items()] + [('var', object, ModifiableConfig())],
    frozen=True
)

但是,我更喜歡能夠選擇哪些屬性是凍結的,哪些不是。 不再需要向數據類添加額外的 class。 它可能看起來像這樣:

config_dataclass_modifiable = dataclasses.make_dataclass(
            'c', [(x, type(x), v, True if 'modifiable' in x else False) for x, v in config.items()])

請注意“如果 x else False 中的 'modifiable' 為真”,我並不是說我最終會這樣做,但希望這有助於更好地理解我的問題。

調整屬性處理的常規方法是編寫自定義__setattr__方法,該方法允許您覆蓋屬性分配的默認行為。 不幸的是,該方法也是數據類掛鈎以強制執行frozen邏輯的方法,該方法有效地鎖定 function 通過拋出TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig來防止進一步更改。

因此,我看不到您的問題沒有直接簡單的解決方案。 在我看來,您將 class 的可變部分委托給內部 object 或字典的方法一點也不差或不符合pythonic,但是如果您可以從需求列表中刪除frozen並且只想要部分-可變數據類,您可以嘗試在此處使用此 bootleg-semi-frozen 配方,該配方使用標志semi更新dataclass裝飾器,您可以打開該標志以獲得您描述的行為:

from dataclasses import dataclass as dc
from traceback import format_stack

def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,
              unsafe_hash=False, frozen=False, semi=False):

    def wrap(cls):
        # sanity checks for new kw
        if semi:
            if frozen:
                raise AttributeError("Either semi or frozen, not both.")
            if cls.__setattr__ != cls.mro()[1].__setattr__:
                raise AttributeError("No touching setattr when using semi!")

        # run original dataclass decorator
        dc(cls, init=init, repr=repr, eq=eq, order=order,
           unsafe_hash=unsafe_hash, frozen=frozen)

        # add semi-frozen logic
        if semi:
            def __setattr__(self, key, value):
                if key in self.__slots__:
                    caller = format_stack()[-2].rsplit('in ', 1)[1].strip()
                    if caller != '__init__':
                        raise TypeError(f"Attribute '{key}' is immutable!")
                object.__setattr__(self, key, value)
            cls.__setattr__ = __setattr__

        return cls

    # Handle being called with or without parens
    if _cls is None:
        return wrap
    return wrap(_cls)

我在這里很簡短,不會在這里解決一些潛在的邊緣情況。 有更好的方法來處理包裝,以便內部更加一致,但它會使這個已經很復雜的片段更加復雜。

有了這個新的dataclass裝飾器,您可以像這樣使用它來定義一個具有一些不可變屬性和一些可變屬性的數據類:

>>> @dataclass(semi=True)
... class Foo:
...     # put immutable attributes and __dict__ into slots 
...     __slots__ = ('__dict__', 'x', 'y')
...     x: int
...     y: int
...     z: int
...
>>> f = Foo(1, 2, 3)
>>> f        # prints Foo(x=1, y=2, z=3)
>>> f.z = 4  # will work
>>> f.x = 4  # raises TypeError: attribute 'x' is immutable!

您不必使用__slots__將可變部分與不可變部分分開,但出於一些原因(例如作為不屬於默認數據類repr的元屬性),它很方便,並且對我來說很直觀。

在上面的最佳答案中,如果Foo是另一個 class 的子類,則代碼會中斷。 要解決此問題,請使用以下行:

super(type(self), self).__setattr__(key, value)

應該讀:

super(type(cls), cls).__setattr__(key, value)

這樣,super 實際上是向上遍歷而不是進入無限的自引用。

暫無
暫無

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

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