Как сделать атрибут экземпляра класса данных непубличным и __init__ arg?

Если я хочу, чтобы атрибут экземпляра был:

  • Непубличный (он же имеет одно начальное подчеркивание)
  • Быть параметром в подписи __init__

Обычно я бы сделал так:

class Foo:
    def __init__(self, bar: str):
        self._bar = bar

foo = Foo(bar="bar")  # foo.bar would raise an AttributeError

Однако в dataclasses я не знаю, как это сделать.

from dataclasses import dataclass

@dataclass
class Foo:
    bar: str  # This leaves bar as a public instance attribute

Как правильно это сделать в dataclasses.dataclass?


person Intrastellar Explorer    schedule 13.11.2020    source источник
comment
Если у вас есть частные атрибуты, вам, вероятно, не следует использовать dataclasses. Класс данных предназначен быть просто держателем данных (отсюда и название данных), а не чем-то с непрозрачным частным состоянием.   -  person user2357112 supports Monica    schedule 13.11.2020
comment
Да, собираюсь второй, что на самом деле. Я нашел здесь объяснение того, как это сделать (см. решение № 5), но это не кажется хорошей идеей. florimond.dev/blog/articles/2018 /10/   -  person Alex Watt    schedule 13.11.2020
comment
Да, я только что прочитал эту статью. Кажется, это ответ, но я согласен, это громоздко. Думаю, мне следует придерживаться обычного класса для моего варианта использования, спасибо вам обоим!   -  person Intrastellar Explorer    schedule 13.11.2020


Ответы (3)


Если вы хотите, чтобы это был аргумент __init__(), просто напишите свой собственный, который предотвратит его автоматическую генерацию. Обратите внимание, что указание init=False, как показано ниже, на самом деле не требуется, поскольку этого в любом случае не произошло бы, но, тем не менее, кажется хорошим способом привлечь внимание к тому, что происходит. По той же причине указывать field(init=False) для частного поля _bar не нужно.

from dataclasses import dataclass, field


@dataclass(init=False)
class Foo:
    def __init__(self, bar: str):
        self._bar = bar

    _bar: str = field(init=False)


foo = Foo(bar="xyz")
print(foo._bar)  # -> xyz
print(foo.bar)  # -> AttributeError: 'Foo' object has no attribute 'bar'
person martineau    schedule 16.11.2020

Это относительно простой случай для методов InitVar и __post_init__. (Хотя многословие, вероятно, не то, что вы имели в виду.)

from dataclasses import dataclass, InitVar


@dataclass
class Foo:
    bar: InitVar[str]

    def __post_init__(self, bar):
        self._bar = bar

Это ведет себя так, как вы описали. bar (как написано) является обязательным аргументом для __init__, но не создает атрибут с именем bar.

>>> f = Foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'bar'
>>> f = Foo(9)
>>> f._bar
9
>>> f.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'

Чтобы было ясно, это то, что вы могли бы использовать в существующем классе данных; вы бы не решили создать класс данных только для этого.

person chepner    schedule 16.11.2020

Здесь вы можете найти большое обсуждение использования @property в классах данных, которые могут решить вашу проблема (_bar безопасен под сеттером/геттером).

Могут быть некоторые проблемы с нежелательными атрибутами, отображаемыми как выходные данные __repr__ или asdict(), которые можно решить, как здесь


PS Я не могу добавить комментарий, поэтому я добавил новый ответ.

person Roman Zhuravlev    schedule 17.11.2020