1

I'm trying create dataclass with optional attribute is_complete:

from dataclasses import dataclass
from typing import Optional


@dataclass(frozen=True)
class MyHistoricCandle:
    open: float
    high: float
    low: float
    close: float
    volume: int
    time: datetime
    is_complete: Optional[bool]

But when i init MyHistoricCandle object without is_complete attribute:

MyHistoricCandle(open=1, high=1, low=1, close=1, volume=1, time=datetime.now())

Getting this error:

TypeError: MyHistoricCandle.__init__() missing 1 required positional argument: 'is_complete'

Question: Is it even possible to create dataclass with optional attribute? I tried is_complete: Optional[bool] = None , but sometimes i don't want add this field instead of setting None value

555Russich
  • 354
  • 2
  • 10
  • What behavior do you want from an instance if you don't pass the `is_complete` argument? – mgilson Jan 18 '23 at 13:47
  • @mgilson create object without `is_complete` attribute – 555Russich Jan 18 '23 at 13:49
  • `dataclass` does not infer that the default value of an `Optional[a]` field should be `None`; you have to be explicit. That said, are you making a three-way distinction between `True`, `False`, and `None` for this field, or should you use `is_complete: bool = False` instead? – chepner Jan 18 '23 at 14:07
  • `dataclass` doesn't even *look* at the types (with a couple of exceptions); only the *presence* of *some* type is used to indicate that a name should be used to generate a field. – chepner Jan 18 '23 at 14:09
  • 1
    Defining a class whose *interface* has optional parts is frowned upon, and not supported. If you want objects that don't have an `is_complete` attribute, they should not have type `MyHistoricCandle`. – chepner Jan 18 '23 at 14:13
  • 3
    `Optional[bool]` doesn't make the attribute optional; it means a *value* of type `bool` is optional for the attribute. `Optional[bool]` is just shorthand for `Union[bool, None]`. – chepner Jan 18 '23 at 14:17
  • 1
    If `x` has type `MyHistoricCandle`, then `x.is_complete` should always produce a value, not raise an `AttributeError` depending on how you initialized the object. – chepner Jan 18 '23 at 14:18

1 Answers1

3

By using dataclass, you are committing to a certain level of rigor regarding the interface to your class. All instances of MyHistoricCandle should have an is_complete attribute, not just some. If you don't want that to be the case, you should probably define two separate classes, one with the attribute and one without.

But, you can work around it by not declaring is_complete as a field, but as an explicit attribute initialized by an init-only variable with a default value of None.

from dataclasses import dataclass, InitVar


@dataclass(frozen=True)
class MyHistoricCandle:
    open: float
    high: float
    low: float
    close: float
    volume: int
    time: datetime
    is_complete: InitVar[Optional[bool]] = None

    def __post_init__(self, is_complete):
        if is_complete is not None:
            self.is_complete = is_complete

As is_complete is not a field, it will be your responsibility to override any of the autogenerated methods (__repr__, __eq__, etc) to take into account the value of self.is_complete.



(Below is a previous answer, when I thought the question was about providing a default value for the attribute when none was passed to __init__.)

TL;DR The type does not make an attribute optional; the presence of a default value does.


dataclass doesn't use the specified type for anything*, much less infer a default value for the field if you don't supply an argument when constructing the value. If you want to omit is_complete from the call, you need to specify what value should be used in its place, whether that value be True, False, or None.

@dataclass(frozen=True)
class MyHistoricCandle:
    open: float
    high: float
    low: float
    close: float
    volume: int
    time: datetime
    is_complete: Optional[bool] = None

* With the exception of InitVar and ClassVar.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thank you for such a good explanation and showing workarounds! For anyone else reading this: check both @chepner answers and comments too – 555Russich Jan 18 '23 at 14:34
  • I can probably delete the other answer. I posted that before I understood that you didn't want an `is_complete` attribute *at all* on the instance. (The answer isn't *bad*, per se, but it's not an answer to the question you asked.) – chepner Jan 18 '23 at 14:37
  • may be you should insert first row from other answer at the end of this answer? – 555Russich Jan 18 '23 at 14:41
  • I think it's good to have that content, because it makes sense as a way to address the underlying problem. The process of turning a problem into a question, sometimes involves making a design choice, which (with the foresight of someone able to answer) might not be optimal. – Karl Knechtel Jan 19 '23 at 01:17
  • But aside from that: does anything about either the problem statement or the proposed solutions, *actually relate to* the dataclass being `frozen`? – Karl Knechtel Jan 19 '23 at 01:18
  • @KarlKnechtel I think you are absolutely right relatively `frozen` in this question. **Edited title and question's text**. When i was asking this question i thought that dataclass has this behavior only with `frozen=True`, but dataclass working like that with any parameters _how i understend it now_ – 555Russich Jan 20 '23 at 21:20