d_j
d_j

Reputation: 1179

__annotations__ doesn't return fields from a parent dataclass

I'm learning about dataclass and I'm having problem with understanding why __annotations__ doesn't give the fields from the parent class. See the example below:

import dataclasses as dc

@dc.dataclass
class B:
    a: int

B.__annotations__
# returns {'a': int}

C = dc.make_dataclass("c", fields=["w"], bases=(B,))
C.__annotations__
# returns {'w': 'typing.Any'} (without a)

Upvotes: 1

Views: 1612

Answers (1)

Arne
Arne

Reputation: 20237

__annotations__ doesn't give you the type annotations of the parent class because it is supposed to only hold the annotations that were defined in the class body of itself. There is a specific function that returns all annotations of a class, including those of its parents, called typing.get_type_hints:

This is often the same as obj.__annotations__. [...] For a class C, return a dictionary constructed by merging all the __annotations__ along C.__mro__ in reverse order.

One thing to keep in mind when using it with your specific example is that dataclasses use a lot of black magic to construct classes, and it will break in finding all type hints if some fields are untyped in the make_dataclass definition (filed as a bug here):

import dataclasses as dc
from typing import Any, get_type_hints

@dc.dataclass
class B:
    a: int

get_type_hints(B)
# returns {'a': <class 'int'>}

# fields=["w"] should be equivalent, but get_type_hints doesn't like it. Bug, maybe?
C = dc.make_dataclass("C", fields=[("w", Any)], bases=(B,))

typing.get_type_hints(C)
# returns {'a': <class 'int'>, 'w': typing.Any}

But as user2357112 pointed out, you might be best advised to use the dataclasses.fields function, which returns the fields that the dataclass decorator builds based on the annotations of the dataclass and its bases. This is usually what you would want to work with when analyzing dataclasses, and it contains all the information you need plus some more.

Additionally, it works with the shorthand definition for fields in make_dataclass that you used initially, and cleans out pseudo-fields:

dc.fields(D)
# returns a tuple of 
Field(name='a',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f103d9985c0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f103d9985c0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)
# and
Field(name='w',type=typing.Any,default=<dataclasses._MISSING_TYPE object at 0x7f103d9985c0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f103d9985c0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)

Upvotes: 2

Related Questions