Объект в стиле Dataclass с изменяемыми и неизменяемыми свойствами? - PullRequest
1 голос
/ 24 октября 2019

Я играл с классами данных, динамически загружаемыми с именами свойств из файла, и я не могу найти способ создания как «замороженных», так и «не замороженных» свойств. Я полагаю, что классы данных позволяют вам устанавливать все свойства как замороженные или не замороженные.

На данный момент я создаю замороженный класс данных и добавляю изменяемый класс в качестве одного из свойств, которые я могу изменить по мере продвижения, ноЯ не очень доволен удобочитаемостью этого подхода.

Есть ли другие люди, которые рекомендуют Python класс данных, без необходимости реализовывать класс с возможностью устанавливать изменяемые / неизменяемые свойства?

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
)

Однако я бы предпочел возможность выбирать, какие атрибуты заморожены, а какие нет. Устранение необходимости добавления дополнительного класса в класс данных. Это может выглядеть так:

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, если« модифицируемый »в x, иначе False», я не говорю, что именно так я бы и сделал в конце, но, надеюсь, это поможет понять моивопрос лучше.

1 Ответ

0 голосов
/ 30 октября 2019

Обычный подход к настройке обработки атрибутов заключается в написании пользовательского метода __setattr__, который позволяет переопределить поведение по умолчанию для назначений атрибутов. К сожалению, этот метод также используется для реализации классов данных для обеспечения логики frozen, которая эффективно блокирует дальнейшее изменение функции, бросая TypeError: Cannot overwrite attribute __setattr__ in class ModifiableConfig, как только вы пытаетесь прикоснуться к ней.

КакКак следствие, я не вижу прямого и простого решения вашей проблемы. Ваш подход к делегированию изменяемых частей класса внутреннему объекту или словарю, на мой взгляд, совсем не плохой и не пифоновый, но если вы согласны удалить 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!")
                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)

Я здесь краткий и рассматриваю только те случаи, которые кажутся мне вероятными недостатками. Существуют более эффективные способы обработки упаковки, чтобы внутренние компоненты были более согласованными, но это взорвало бы этот и без того сложный фрагмент еще больше.

Учитывая этот новый 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) и чувствуется для меня интуитивно понятным.

...