问题
I have been playing around with dataclasses dynamically loaded with property names from a file and I am unable to find a way to create both 'frozen' and 'non-frozen' properties. I believe dataclasses only allow you to set all properites to frozen or non-frozen.
As of now, I create a frozen dataclass and add a mutable class as one of the properties which I can change as I go but I am not very happy with the readability of this approach.
Is there another pythonic dataclass people would recommend without needing to implement a class with the ability to set mutable/immutable properties?
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
)
However I would prefer the ability to choose which attributes are frozen and which are not. Making the need of adding an additional class to the dataclass obsolete. It may look like this:
config_dataclass_modifiable = dataclasses.make_dataclass(
'c', [(x, type(x), v, True if 'modifiable' in x else False) for x, v in config.items()])
Notice the "True if 'modifiable' in x else False", I'm not saying this is how I would do it in the end but hopefully this helps understand my question better.
回答1:
The normal approach to tuning attribute handling is writing a custom __setattr__ method which allows you to override the default behavior for attribute assignments. Unfortunately, that method is also what dataclasses hooks into to enforce the frozen
logic, which effectively locks the function from being altered any further by throwing TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig
as soon as you try to touch it.
As a consequence, there is no straight forward and simple solution to your problem that I can see. Your approach of delegating the mutable parts of a class to an inner object or dictionary is, in my opinion, not bad or un-pythonic at all, but if you're fine with dropping frozen
from your requirements list and only want a partly-mutable dataclass, you can try using this bootleg-semi-frozen recipe here that updates the dataclass
decorator with a flag semi
that you can switch on to get the behavior you described:
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!")
super(type(self), self).__setattr__(key, value)
cls.__setattr__ = __setattr__
return cls
# Handle being called with or without parens
if _cls is None:
return wrap
return wrap(_cls)
I'm being brief here and only handle the cases that look like probable shortcomings to me. There are better ways to handle the wrapping so that the internals are more consistent, but it would blow this already complicated snippet up even more.
Given this new dataclass
decorator, you can use it like this to define a dataclass with some immutable attributes and some mutable ones:
>>> @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!
You don't have to use __slots__
to separate the mutable from the immutable part, but it is convenient for a few reasons (such as being a meta-attribute that isn't part of the default dataclass repr
) and felt intuitive to me.
来源:https://stackoverflow.com/questions/58532383/dataclass-style-object-with-mutable-and-immutable-properties