For classes in general, you can access the __annotations__:

>>> class Foo:
...    bar: int
...    baz: str
...
>>> Foo.__annotations__
{'bar': <class 'int'>, 'baz': <class 'str'>}

This returns a dict mapping attribute name to annotation.

However, dataclasses use dataclasses.Field objects to encapsulate a lot of this information. You can use dataclasses.fields on an instance or on the class:

>>> import dataclasses
>>> @dataclasses.dataclass
... class Foo:
...     bar: int
...     baz: str
...
>>> dataclasses.fields(Foo)
(Field(name='bar',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='baz',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))

NOTE:

Starting in Python 3.7, the evaluation of annotations can be postponed:

>>> from __future__ import annotations
>>> class Foo:
...     bar: int
...     baz: str
...
>>> Foo.__annotations__
{'bar': 'int', 'baz': 'str'} 

note, the annotation is kept as a string, this also affects dataclasses as well:

>>> @dataclasses.dataclass
... class Foo:
...     bar: int
...     baz: str
...
>>> dataclasses.fields(Foo)
(Field(name='bar',type='int',default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='baz',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))

So, just be aware, since this will become the standard behavior, code you write should probably use the __future__ import and work under that assumption, because in Python 3.10, this will become the standard behavior.

The motivation behind this behavior is that the following currently raises an error:

>>> class Node:
...    def foo(self) -> Node:
...        return Node()
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in Node
NameError: name 'Node' is not defined

But with the new behavior:

>>> from __future__ import annotations
>>> class Node:
...     def foo(self) -> Node:
...         return Node()
...
>>>

One way to handle this is to use the typing.get_type_hints, which I believe just basically eval's the type hints:

>>> import typing
>>> typing.get_type_hints(Node.foo)
{'return': <class '__main__.Node'>}
>>> class Foo:
...    bar: int
...    baz: str
...
>>> Foo.__annotations__
{'bar': 'int', 'baz': 'str'}
>>> import typing
>>> typing.get_type_hints(Foo)
{'bar': <class 'int'>, 'baz': <class 'str'>}

Not sure how reliable this function is, but basically, it handles getting the appropriate globals and locals of where the class was defined. So, consider:

(py38) juanarrivillaga@Juan-Arrivillaga-MacBook-Pro ~ % cat test.py
from __future__ import annotations

import typing

class Node:
    next: Node

(py38) juanarrivillaga@Juan-Arrivillaga-MacBook-Pro ~ % python
Python 3.8.5 (default, Sep  4 2020, 02:22:02)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.Node
<class 'test.Node'>
>>> import typing
>>> typing.get_type_hints(test.Node)
{'next': <class 'test.Node'>}

Naively, you might try something like:

>>> test.Node.__annotations__
{'next': 'Node'}
>>> eval(test.Node.__annotations__['next'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'Node' is not defined

You could hack together something like:

>>> eval(test.Node.__annotations__['next'], vars(test))
<class 'test.Node'>

But it can get tricky

Answer from juanpa.arrivillaga on Stack Overflow
Top answer
1 of 2
21

For classes in general, you can access the __annotations__:

>>> class Foo:
...    bar: int
...    baz: str
...
>>> Foo.__annotations__
{'bar': <class 'int'>, 'baz': <class 'str'>}

This returns a dict mapping attribute name to annotation.

However, dataclasses use dataclasses.Field objects to encapsulate a lot of this information. You can use dataclasses.fields on an instance or on the class:

>>> import dataclasses
>>> @dataclasses.dataclass
... class Foo:
...     bar: int
...     baz: str
...
>>> dataclasses.fields(Foo)
(Field(name='bar',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='baz',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))

NOTE:

Starting in Python 3.7, the evaluation of annotations can be postponed:

>>> from __future__ import annotations
>>> class Foo:
...     bar: int
...     baz: str
...
>>> Foo.__annotations__
{'bar': 'int', 'baz': 'str'} 

note, the annotation is kept as a string, this also affects dataclasses as well:

>>> @dataclasses.dataclass
... class Foo:
...     bar: int
...     baz: str
...
>>> dataclasses.fields(Foo)
(Field(name='bar',type='int',default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='baz',type='str',default=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f806369bc10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))

So, just be aware, since this will become the standard behavior, code you write should probably use the __future__ import and work under that assumption, because in Python 3.10, this will become the standard behavior.

The motivation behind this behavior is that the following currently raises an error:

>>> class Node:
...    def foo(self) -> Node:
...        return Node()
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in Node
NameError: name 'Node' is not defined

But with the new behavior:

>>> from __future__ import annotations
>>> class Node:
...     def foo(self) -> Node:
...         return Node()
...
>>>

One way to handle this is to use the typing.get_type_hints, which I believe just basically eval's the type hints:

>>> import typing
>>> typing.get_type_hints(Node.foo)
{'return': <class '__main__.Node'>}
>>> class Foo:
...    bar: int
...    baz: str
...
>>> Foo.__annotations__
{'bar': 'int', 'baz': 'str'}
>>> import typing
>>> typing.get_type_hints(Foo)
{'bar': <class 'int'>, 'baz': <class 'str'>}

Not sure how reliable this function is, but basically, it handles getting the appropriate globals and locals of where the class was defined. So, consider:

(py38) juanarrivillaga@Juan-Arrivillaga-MacBook-Pro ~ % cat test.py
from __future__ import annotations

import typing

class Node:
    next: Node

(py38) juanarrivillaga@Juan-Arrivillaga-MacBook-Pro ~ % python
Python 3.8.5 (default, Sep  4 2020, 02:22:02)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.Node
<class 'test.Node'>
>>> import typing
>>> typing.get_type_hints(test.Node)
{'next': <class 'test.Node'>}

Naively, you might try something like:

>>> test.Node.__annotations__
{'next': 'Node'}
>>> eval(test.Node.__annotations__['next'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'Node' is not defined

You could hack together something like:

>>> eval(test.Node.__annotations__['next'], vars(test))
<class 'test.Node'>

But it can get tricky

2 of 2
4

Check this out:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

Point.__annotations__ returns {'x': <class 'int'>, 'y': <class 'int'>}.

🌐
Python
docs.python.org › 3 › library › dataclasses.html
dataclasses — Data Classes
February 23, 2026 - To determine whether a field contains a default value, @dataclass will call the descriptor’s __get__() method using its class access form: descriptor.__get__(obj=None, type=cls). If the descriptor returns a value in this case, it will be used as the field’s default. On the other hand, if the descriptor raises AttributeError in this situation, no default value will be provided for the field.
Discussions

Print all values within a class?
class Name: def __init__(self): self.a = "a" self.b = "b" self.c = "c" def printall(self): for k,v in self.__dict__.items(): print(f"{str(k)} = {str(v)}") obj = Name() obj.printall() Also self.1='a' is not valid! More on reddit.com
🌐 r/learnpython
16
11
July 13, 2022
python - How do I pull out the attributes or field names from a dataclass? - Stack Overflow
I have a dataclass, say Car: from dataclasses import dataclass @dataclass class Car: make: str model: str color: hex owner: Owner How do I pull out the attributes or fields of the More on stackoverflow.com
🌐 stackoverflow.com
How can I get Python 3.7 new dataclass field types? - Stack Overflow
Python 3.7 introduces new feature called data classes. from dataclasses import dataclass @dataclass class MyClass: id: int = 0 name: str = '' When using type hints (annotation) in function More on stackoverflow.com
🌐 stackoverflow.com
python - Get the name of all fields in a dataclass - Stack Overflow
I am trying to write a function to log dataclasses I would like to get the name of all fields in the dataclass and print the value to each (similar to how you might write a function to print a dict... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Python.org
discuss.python.org › typing
Treatment of `Final` attributes in dataclass-likes - Typing - Discussions on Python.org
February 28, 2024 - (redirection from my post at pyright) ... within class body are to be interpreted as: class-level constants, if they have a value assigned, instance-level constants, if the value ......
🌐
Ivergara
ivergara.github.io › deeper-dataclasses.html
Deeper into dataclasses - On data, programming, and technology
August 5, 2019 - We have to add/delete attributes/fields definitions and all references to them in the relevant methods. In this particular case, that would be six lines modified. The first part is the easy and the second one (very) annoying. We could do slightly better and only having to care for the first part if we want to address other dimensions. Let’s be slightly smarter and use more of the tools available in the dataclasses module.
🌐
Glarity
askai.glarity.app › search › How-can-I-list-all-attributes-of-a-dataclass-in-Python
How can I list all attributes of a dataclass in Python? - Ask and Answer - Glarity
3. **Using `dataclasses.fields`**: Alternatively, you can use the `fields` function from the `dataclasses` module to get a list of field objects, which can then be used to retrieve the names of the attributes: ... 4. **Considerations**: Remember that the attributes listed are the ones defined in the dataclass. If you have instance-specific attributes (attributes added after the instance is created), those won't be included in the dataclass fields. By following these steps, you can easily list all the attributes of a dataclass in Python.
🌐
Dataquest
dataquest.io › blog › how-to-use-python-data-classes
How to Use Python Data Classes (A Beginner's Guide) – Dataquest
May 12, 2025 - To achieve that, we'll take advantage of two other features of the dataclasses module. The first is the field function. This function is used to customize one attribute of a data class individually, which allows us to define new attributes that will depend on another attribute and will only be created after the object is instantiated.
Find elsewhere
🌐
Florimond Manca
florimond.dev › en › posts › 2018 › 10 › reconciling-dataclasses-and-properties-in-python
Reconciling Dataclasses And Properties In Python - Florimond Manca
October 10, 2018 - In short, dataclasses are a simple, elegant, Pythonic way of creating classes that hold data. 🐍 ... I sometimes resort to the @property decorator to implement specific logic when getting/setting an attribute.
🌐
GeeksforGeeks
geeksforgeeks.org › python › how-to-get-a-list-of-class-attributes-in-python
How to Get a List of Class Attributes in Python? - GeeksforGeeks
July 12, 2025 - Number has three class attributes and one instance attribute set in __init__. The show() method prints all attributes. Creating n with attr=2 and calling n.show() displays them while vars(n) shows only the instance attributes.
🌐
CodeRivers
coderivers.org › blog › python-get-all-keys-from-dataclass
Python: Getting All Keys from a Dataclass - CodeRivers
January 23, 2025 - The built-in vars() function returns a dictionary representing the object's attributes. For a dataclass instance, we can use it to get all the attribute names (keys).
Top answer
1 of 3
127

Inspecting __annotations__ gives you the raw annotations, but those don't necessarily correspond to a dataclass's field types. Things like ClassVar and InitVar show up in __annotations__, even though they're not fields, and inherited fields don't show up.

Instead, call dataclasses.fields on the dataclass, and inspect the field objects:

field_types = {field.name: field.type for field in fields(MyClass)}

Neither __annotations__ nor fields will resolve string annotations. If you want to resolve string annotations, the best way is probably typing.get_type_hints. get_type_hints will include ClassVars and InitVars, so we use fields to filter those out:

resolved_hints = typing.get_type_hints(MyClass)
field_names = [field.name for field in fields(MyClass)]
resolved_field_types = {name: resolved_hints[name] for name in field_names}
2 of 3
58
from dataclasses import dataclass

@dataclass
class MyClass:
    id: int = 0
    name: str = '' 

myclass = MyClass()

myclass.__annotations__
>> {'id': int, 'name': str}
myclass.__dataclass_fields__
>> {'id': Field(name='id',type=<class 'int'>,default=0,default_factory=<dataclasses._MISSING_TYPE object at 0x0000000004EED668>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD),
 'name': Field(name='name',type=<class 'str'>,default='',default_factory=<dataclasses._MISSING_TYPE object at 0x0000000004EED668>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)}

on a side note there is also:

myclass.__dataclass_params__
>>_DataclassParams(init=True,repr=True,eq=True,order=False,unsafe_hash=False,frozen=False)
🌐
Medium
martinxpn.medium.com › data-classes-in-python-52-100-days-of-python-cf4c2317394b
Data Classes in Python (52/100 Days of Python) | by Martin Mirakyan | Medium
April 10, 2023 - Data Classes in Python (52/100 Days of Python) Dataclasses in Python provide a simple and concise way to define classes with default attributes, methods, and properties. In this tutorial, we will …
Top answer
1 of 4
31

This example shows only a name, type and value, however, __dataclass_fields__ is a dict of Field objects, each containing information such as name, type, default value, etc.

Using dataclasses.fields()

Using dataclasses.fields() you can access fields you defined in your dataclass.

fields = dataclasses.fields(dataclass_instance)

Using inspect.getmembers()

Using inspect.getmembers() you can access all fields in your dataclass.

members = inspect.getmembers(type(dataclass_instance))
fields = list(dict(members)['__dataclass_fields__'].values())

Complete code solution

import dataclasses
import inspect


@dataclasses.dataclass
class Test:
    a: str = "a value"
    b: str = "b value"


def print_data_class(dataclass_instance):

    # option 1: fields
    fields = dataclasses.fields(dataclass_instance)

    # option 2: inspect
    members = inspect.getmembers(type(dataclass_instance))
    fields = list(dict(members)['__dataclass_fields__'].values())

    for v in fields:
        print(f'{v.name}: ({v.type.__name__}) = {getattr(dataclass_instance, v.name)}')


print_data_class(Test())
# a: (str) = a value
# b: (str) = b value

print_data_class(Test(a="1", b="2"))
# a: (str) = 1
# b: (str) = 2
2 of 4
9

Also, you can use __annotations__, well, because data fields are always annotated. This is the essense of dataclasses usage.

It works with classes

    fields = list(Test.__annotations__)

and with instances

    fields = list(test.__annotations__)

There should be noted that it doesn't work with dataclass subclasses. Obviously. However, simplicity gives you fields names directly, without extra code for extraction from Field objects.

🌐
Real Python
realpython.com › python-data-classes
Data Classes in Python (Guide) – Real Python
March 8, 2024 - A Python dataclass lets you define classes for storing data with less boilerplate. Use @dataclass to generate .__init__(), .__repr__(), and .__eq__() automatically. Dataclasses allow you to create classes quickly, but you can also add defaults, custom methods, ordering, immutability, inheritance, and even slots.
🌐
Medium
medium.com › alan › 5-things-you-should-know-about-dataclass-8c143b75596
🐍 5 things you should know about @dataclass | by Benoit Prioux | Alan Product and Technical Blog | Medium
July 5, 2022 - Equality: by default, the equality is implemented based on all the attributes. repr: there is a default string representation displaying attributes with their value. The main advantage is when you add a new attribute, nothing to update: constructor, equality and repr are up to date! 🎉 · 💡 Take a look at https://www.pythonmorsels.com/undataclass/, it’s a script generating the corresponding code to a dataclass.
🌐
Real Python
realpython.com › ref › stdlib › dataclasses
dataclasses | Python Standard Library – Real Python
>>> from dataclasses import asdict, dataclass >>> @dataclass ... class Point: ... x: int ... y: int ... >>> point = Point(5, 6) >>> asdict(point) {'x': 5, 'y': 6} Facilitating the creation of classes that are mainly used for storing data · Automatically generating common methods like .__init__(), .__repr__(), and .__eq__() Converting data class instances to dictionaries or tuples for serialization · Suppose you need to manage a collection of employees with attributes like .name, .age, and .position.
Top answer
1 of 2
2

The dataclasses module doesn't provide built-in support for this use case, i.e. loading YAML data to a nested class model.

In such a scenario, I would turn to a ser/de library such as dataclass-wizard, which provides OOTB support for (de)serializing YAML data, via the PyYAML library.

Disclaimer: I am the creator and maintener of this library.

Step 1: Generate a Dataclass Model

Note: I will likely need to make this step easier for generating a dataclass model for YAML data. Perhaps worth creating an issue to look into as time allows. Ideally, usage is from the CLI, however since we have YAML data, it is tricky, because the utility tool expects JSON.

So easiest to do this in Python itself, for now:

from json import dumps

# pip install PyYAML dataclass-wizard
from yaml import safe_load
from dataclass_wizard.wizard_cli import PyCodeGenerator

yaml_string = """
account: 12345
clusters:
  - name: cluster_1
    endpoint: https://cluster_2
    certificate: abcdef
  - name: cluster_1
    endpoint: https://cluster_2
    certificate: abcdef
"""

py_code = PyCodeGenerator(experimental=True, file_contents=dumps(safe_load(yaml_string))).py_code
print(py_code)

Prints:

from __future__ import annotations

from dataclasses import dataclass

from dataclass_wizard import JSONWizard


@dataclass
class Data(JSONWizard):
    """
    Data dataclass

    """
    account: int
    clusters: list[Cluster]


@dataclass
class Cluster:
    """
    Cluster dataclass

    """
    name: str
    endpoint: str
    certificate: str

Step 2: Use Generated Dataclass Model, alongside YAMLWizard

Contents of my_file.yml:

account: 12345
clusters:
  - name: cluster_1
    endpoint: https://cluster_5
    certificate: abcdef
  - name: cluster_2
    endpoint: https://cluster_7
    certificate: xyz

Python code:

from __future__ import annotations

from dataclasses import dataclass
from pprint import pprint

from dataclass_wizard import YAMLWizard


@dataclass
class Data(YAMLWizard):
    account: int
    clusters: list[Cluster]


@dataclass
class Cluster:
    name: str
    endpoint: str
    certificate: str


data = Data.from_yaml_file('./my_file.yml')
pprint(data)
for c in data.clusters:
    print(c.endpoint)

Result:

Data(account=12345,
     clusters=[Cluster(name='cluster_1',
                       endpoint='https://cluster_5',
                       certificate='abcdef'),
               Cluster(name='cluster_2',
                       endpoint='https://cluster_7',
                       certificate='xyz')])
https://cluster_5
https://cluster_7
2 of 2
1

As Barmar points out in a comment, even though you have correctly typed the _clusters key in your AWSInfo dataclass...

@dataclass
class AWSInfo:
    _account: int
    _clusters: list[ClusterInfo]

...the dataclasses module isn't smart enough to automatically convert the members of the clusters list in in your input data into the appropriate data type. If you use a more comprehensive data model library like Pydantic, things will work like you expect:

import yaml
from pydantic import BaseModel

class ClusterInfo(BaseModel):
    name: str
    endpoint: str
    certificate: str

class AWSInfo(BaseModel):
    account: int
    clusters: list[ClusterInfo]


with open('clusters.yml', 'r') as fd:
    clusters = yaml.safe_load(fd)
a = AWSInfo(**clusters)

print(a.account) #prints 12345
print(a.clusters) #prints the dict of both clusters
print(a.clusters[0]) #prints the dict of the first cluster

#These prints fails with AttributeError: 'dict' object has no attribute '_endpoint'
print(a.clusters[0].endpoint)
for c in a.clusters:
    print(c.endpoint)

Running the above code (with your sample input) produces:

12345
[ClusterInfo(name='cluster_1', endpoint='https://cluster_2', certificate='abcdef'), ClusterInfo(name='cluster_1', endpoint='https://cluster_2', certificate='abcdef')]
name='cluster_1' endpoint='https://cluster_2' certificate='abcdef'
https://cluster_2
https://cluster_2
https://cluster_2