具有可变和不可变属性的数据类样式对象?
Dataclass-style object with mutable and immutable properties?
我一直在玩弄数据classes 动态加载文件中的 属性 名称,但我无法找到同时创建 'frozen' 和 [=24] 的方法=] 属性。我相信 dataclasses 只允许您将所有属性设置为冻结或非冻结。
截至目前,我创建了一个冻结数据class 并添加了一个可变 class 作为我可以随时更改的属性之一,但我对它的可读性不是很满意这种方法。
是否有其他 pythonic 数据class 人们会推荐,而无需实现具有设置 mutable/immutable 属性的 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 添加额外的 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()])
请注意 "True if 'modifiable' in x else False",我并不是说我最终会这样做,但希望这有助于更好地理解我的问题。
调整属性处理的常规方法是编写一个自定义 __setattr__
方法,它允许您覆盖属性分配的默认行为。不幸的是,该方法也是 dataclasses 挂钩以强制执行 frozen
逻辑的方法,它通过在您尝试触摸时立即抛出 TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig
来有效地锁定函数以防止进一步更改它。
因此,我看不到针对您的问题的直接简单的解决方案。在我看来,您将 class 的可变部分委托给内部对象或字典的方法一点也不糟糕或非 Pythonic,但是如果您可以从您的 frozen
中删除需求列表并且只想要一个部分可变的数据class,你可以尝试使用这个 bootleg-semi-frozen 配方在这里更新 dataclass
装饰器带有你可以打开的标志 semi
获得您描述的行为:
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
装饰器,您可以像这样使用它来定义一个数据class,其中包含一些不可变属性和一些可变属性:
>>> @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__
将可变部分与不可变部分分开,但出于某些原因(例如作为不属于默认部分的元属性)很方便dataclass repr
) 并且对我来说很直观。
在上面的最佳答案中,如果 Foo
是另一个 class 的子class,代码就会中断。要解决此问题,行:
super(type(self), self).__setattr__(key, value)
应改为:
super(type(cls), cls).__setattr__(key, value)
那样的话,super实际上是向上遍历,而不是进入无限自引用。
我一直在玩弄数据classes 动态加载文件中的 属性 名称,但我无法找到同时创建 'frozen' 和 [=24] 的方法=] 属性。我相信 dataclasses 只允许您将所有属性设置为冻结或非冻结。
截至目前,我创建了一个冻结数据class 并添加了一个可变 class 作为我可以随时更改的属性之一,但我对它的可读性不是很满意这种方法。
是否有其他 pythonic 数据class 人们会推荐,而无需实现具有设置 mutable/immutable 属性的 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 添加额外的 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()])
请注意 "True if 'modifiable' in x else False",我并不是说我最终会这样做,但希望这有助于更好地理解我的问题。
调整属性处理的常规方法是编写一个自定义 __setattr__
方法,它允许您覆盖属性分配的默认行为。不幸的是,该方法也是 dataclasses 挂钩以强制执行 frozen
逻辑的方法,它通过在您尝试触摸时立即抛出 TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig
来有效地锁定函数以防止进一步更改它。
因此,我看不到针对您的问题的直接简单的解决方案。在我看来,您将 class 的可变部分委托给内部对象或字典的方法一点也不糟糕或非 Pythonic,但是如果您可以从您的 frozen
中删除需求列表并且只想要一个部分可变的数据class,你可以尝试使用这个 bootleg-semi-frozen 配方在这里更新 dataclass
装饰器带有你可以打开的标志 semi
获得您描述的行为:
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
装饰器,您可以像这样使用它来定义一个数据class,其中包含一些不可变属性和一些可变属性:
>>> @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__
将可变部分与不可变部分分开,但出于某些原因(例如作为不属于默认部分的元属性)很方便dataclass repr
) 并且对我来说很直观。
在上面的最佳答案中,如果 Foo
是另一个 class 的子class,代码就会中断。要解决此问题,行:
super(type(self), self).__setattr__(key, value)
应改为:
super(type(cls), cls).__setattr__(key, value)
那样的话,super实际上是向上遍历,而不是进入无限自引用。