Reputation: 540
from __future__ import annotations
from dataclasses import dataclass, is_dataclass, field, fields
@dataclass
class A:
a: int | None = None
b: int | None = None
@dataclass
class B:
a_obj: A = field(default_factory=A)
x: int = 0
y: int = 0
for f in fields(B):
print(is_dataclass(f.type)) # False for all
Is NOT using annotations
the only way out of this, I would have to change a lot of the type annotations to use Optional[T]
instead of T | None
Upvotes: 1
Views: 566
Reputation: 11690
One option is to use an approach with cached class properties; this can also help to work around cases when class A
is defined after B
, for instance.
from __future__ import annotations
from dataclasses import dataclass, is_dataclass, field, fields, Field
from functools import cache
from typing import get_type_hints
def cached_class_attr(f):
return classmethod(property(cache(f)))
@dataclass
class B:
# using lambda because we can't use `A` directly, as it's not defined yet
a_obj: A = field(default_factory=lambda: A())
x: int = 0
y: int = 0
@cached_class_attr
def __true_annotations__(cls):
return get_type_hints(cls)
@cached_class_attr
def __fields__(cls):
ann_dict: dict = cls.__true_annotations__
cls_fields: tuple[Field, ...] = fields(cls)
for f in cls_fields:
f.type = ann_dict[f.name]
return cls_fields
@dataclass
class A:
a: int | None = None
b: int | None = None
for name, tp in B.__true_annotations__.items():
print(name, is_dataclass(tp))
Output:
a_obj True
x False
y False
Upvotes: 1
Reputation: 110631
I checked the code of dataclasses in Python 3.10 and there is no prevision for it to work with stringfied annotations, in the way that happens when one use from __future__ import annotations
.
That is because the idea of stringifying annotations is ok just when using them for static type hinting. Dataclasses and newer Python libs, such as pydantic actually make use of annotations for runtime purposes - that has been the cause of PEP 563, which describes this stringfying effect, not to have been promoted to the default behavior in Python 3.10 - and the contending PEP 649 being put forward.
In this case, you can "re-hydrate" the annotations before passing the class to the dataclass decorator, since it does not do that. -- put this as an intermediate decorator and it should work:
def hydrate(cls):
cls.__annotations__ = typing.get_type_hints(cls)
return cls
and then:
from __future__ import annotations
...
@dataclass
@hydrate
class B:
a_obj: A = field(default_factory=A)
x: int = 0
y: int = 0
Upvotes: 1