Literal is not a normal type that a python object would have at runtime, it doesn't make sense to check that an object is a Literal

You can access the annotations for a class using __annotations__, following on from your example:

>>> Example.__annotations__['answer'].__args__
('Y', 'N')
Answer from Iain Shelvington on Stack Overflow
๐ŸŒ
Python
typing.python.org โ€บ en โ€บ latest โ€บ spec โ€บ dataclasses.html
Dataclasses โ€” typing documentation
Field specifier functions can use overloads that implicitly specify the value of init using a literal bool value type (Literal[False] or Literal[True]). default is an optional parameter that provides the default value for the field. default_factory is an optional parameter that provides a runtime callback that returns the default value for the field. If neither default nor default_factory are specified, the field is assumed to have no default value and must be provided a value when the class is instantiated. factory is an alias for default_factory. Stdlib dataclasses use the name default_factory, but attrs uses the name factory in many scenarios, so this alias is necessary for supporting attrs.
Discussions

How to convert dataclass to dictionary of string (literal)?
You can just call asdict method. In [12]: dataclasses.asdict(MessageHeader(message_id='383b0bfc-743e-4738-8361-27e6a0753b5a')) Out[12]: {'message_id': '383b0bfc-743e-4738-8361-27e6a0753b5a'} More on reddit.com
๐ŸŒ r/learnpython
9
8
June 13, 2022
python - How to create "dynamic" Literal types from dataclass members - Stack Overflow
How to do add support for custom smart type completion in Python? Let's say I have a dataclass: @dataclass class MyData: mine: str yours: str def col(self, value: str): return "The More on stackoverflow.com
๐ŸŒ stackoverflow.com
json - How to convert Python dataclass to dictionary of string literal? - Stack Overflow
Is there any better way to convert a dataclass to a dictionary with string literal like above? ... When I call dict from pydantic, it returns uuid.UUID as it is. I need the UUID as a string in dictionary ... If you don't wanna use 3rd party libraries or even builtins, you probably came to Python ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
python - Create dataclass instance from union type based on string literal - Stack Overflow
We are not committed to dataclasses. I'm a little bit familiar with pydantic, but could you point me to a feature of pydantic that would help us here? ... Ah, I misunderstood the reference to the mypy error as something unexpected. Now I see what your intent was. Yes, I think I can. I'll write answer for you later. (On mobile now.) ... With raw python without Pydantic, you can create overloaded definition, that accepts name (Literal ... More on stackoverflow.com
๐ŸŒ stackoverflow.com
๐ŸŒ
Python
docs.python.org โ€บ 3 โ€บ library โ€บ dataclasses.html
dataclasses โ€” Data Classes
February 23, 2026 - Source code: Lib/dataclasses.py This module provides a decorator and functions for automatically adding generated special methods such as__init__() and__repr__() to user-defined classes. It was ori...
๐ŸŒ
Reddit
reddit.com โ€บ r/learnpython โ€บ how to convert dataclass to dictionary of string (literal)?
r/learnpython on Reddit: How to convert dataclass to dictionary of string (literal)?
June 13, 2022 -

Given a dataclass like below:

@dataclass
class MessageHeader:
    message_id: uuid.UUID

I would like to get a dictionary of string literal when I call dict on MessageHeaderThe desired outcome of dictionary is like below:

{'message_id': '383b0bfc-743e-4738-8361-27e6a0753b5a'}

I want to avoid using 3rd party library like pydantic

Is there any better way to convert a dataclass to a dictionary with string literal like above?

Thanks

๐ŸŒ
Real Python
realpython.com โ€บ python-data-classes
Data Classes in Python (Guide) โ€“ Real Python
March 8, 2024 - Data classes do not implement a .__str__() method, so Python will fall back to the .__repr__() method. Let us implement a user-friendly representation of a PlayingCard: ... from dataclasses import dataclass @dataclass class PlayingCard: rank: str suit: str def __str__(self): return f'{self.suit}{self.rank}'
๐ŸŒ
Dataquest
dataquest.io โ€บ blog โ€บ how-to-use-python-data-classes
How to Use Python Data Classes (A Beginner's Guide) โ€“ Dataquest
May 12, 2025 - Define a class using dataclasses ยท Use default attributes and their rules ยท Create a representation method ยท Compare data classes ยท Sort data classes ยท Use inheritance with data classes ยท Work with immutable data classes ยท Tutorials ยท Read more ยท Read more ยท Learn data skills 10x faster ยท Join 1M+ learners ยท Enroll for free ยท Data Analyst (Python) Gen AI (Python) SQL ยท Data Literacy using Excel ยท
๐ŸŒ
Tom's Blog
tomaugspurger.net โ€บ posts โ€บ serializing dataclasses
Serializing Dataclasses | Tom's Blog
August 31, 2024 - @dataclasses.dataclass class ArrayMetadata: shape: tuple[int, ...] timestamp: datetime.datetime # note 1 @dataclasses.dataclass class EncoderA: value: int @dataclasses.dataclass class EncoderB: value: int @dataclasses.dataclass class Metadata: version: typing.Literal["3"] # note 2 array_metadata: ArrayMetadata # note 2 encoder: EncoderA | EncoderB # note 4 attributes: dict[str, typing.Any] name: str | None = None # note 5 ยท Successfully serializing an instance of Metadata requires working through a few things: Python datetimes are not natively serializable by Pythonโ€™s JSON encoder.
Find elsewhere
๐ŸŒ
Real Python
realpython.com โ€บ ref โ€บ stdlib โ€บ dataclasses
dataclasses | Python Standard Library โ€“ Real Python
The Python dataclasses module provides functionality for creating and working with user-defined data classes.
๐ŸŒ
death and gravity
death.andgravity.com โ€บ dataclasses
Dataclasses without type annotations - death and gravity
The Ellipsis literal is a nice way of saying "I don't care about this value": ... If you still want the dataclass to work with type checking, while not bothering with types yourself, you can use Any: from typing import Any @dataclass class Data: one: Any two: Any = 2 ยท Or, if you don't like the extra import, use object: @dataclass class Data: one: object two: object = 2 ยท This works because everything in Python is an object (figuratively and literally).
๐ŸŒ
Pydantic
docs.pydantic.dev โ€บ latest โ€บ api โ€บ dataclasses
Pydantic Dataclasses - Pydantic Validation
dataclass( *, init: Literal[False] = False, repr: bool = True, eq: bool = True, order: bool = False, unsafe_hash: bool = False, frozen: bool = False, config: ConfigDict | type[object] | None = None, validate_on_init: bool | None = None, kw_only: ...
๐ŸŒ
Stack Overflow
stackoverflow.com โ€บ questions โ€บ 79556755 โ€บ how-to-create-dynamic-literal-types-from-dataclass-members
python - How to create "dynamic" Literal types from dataclass members - Stack Overflow
First point - the Literal strings in value could be represented using an Enum - this would make for essentially an equivalent interface. Enums and Dataclasses can both be generated dynamically - to make the string in "col" support only those in the dataclass, we just need to have them derive from the same class.
๐ŸŒ
InfoWorld
infoworld.com โ€บ home โ€บ software development โ€บ programming languages โ€บ python
How to use Python dataclasses | InfoWorld
October 22, 2025 - Note how we use field to initialize shelf_id, and pass init as False to field. This means shelf_id wonโ€™t be initialized in __init__, but it is registered as a field with the dataclass overall, with type information. Another way to customize Python dataclass setup is to use the InitVar type.
๐ŸŒ
Python
peps.python.org โ€บ pep-0557
PEP 557 โ€“ Data Classes - Python Enhancement Proposals
If the default value of a field is specified by a call to field(), then the class attribute for this field will be replaced by the specified default value. If no default is provided, then the class attribute will be deleted. The intent is that after the dataclass decorator runs, the class attributes will all contain the default values for the fields, just as if the default value itself were specified.
Top answer
1 of 1
5

UPDATE: Using Pydantic v2

If you are willing to switch to Pydantic instead of dataclasses, you can define a discriminated union via typing.Annotated and use the TypeAdapter as a "universal" constructor that is able to discriminate between distinct Event subtypes based on the provided name string.

Here is what I would suggest:

from typing import Annotated, Any, Literal

from pydantic import BaseModel, Field, TypeAdapter


class EventBase(BaseModel):
    name: str
    value: Any


class NumberEvent(EventBase):
    name: Literal["temperature", "line_number"]
    value: float


class StringEvent(EventBase):
    name: Literal["warning", "status"]
    value: str


Event = TypeAdapter(Annotated[
    NumberEvent | StringEvent,
    Field(discriminator="name"),
])


event_temp = Event.validate_python({"name": "temperature", "value": 3.14})
event_status = Event.validate_python({"name": "status", "value": "spam"})

print(repr(event_temp))    # NumberEvent(name='temperature', value=3.14)
print(repr(event_status))  # StringEvent(name='status', value='spam')

An invalid name would of course cause a validation error, just like a completely wrong and type for value (that cannot be coerced). Example:

from pydantic import ValidationError

try:
    Event.validate_python({"name": "temperature", "value": "foo"})
except ValidationError as err:
    print(err.json(indent=4))

try:
    Event.validate_python({"name": "foo", "value": "bar"})
except ValidationError as err:
    print(err.json(indent=4))

Output:

[
    {
        "type": "float_parsing",
        "loc": [
            "temperature",
            "value"
        ],
        "msg": "Input should be a valid number, unable to parse string as a number",
        "input": "foo",
        "url": "https://errors.pydantic.dev/2.1/v/float_parsing"
    }
]
[
    {
        "type": "union_tag_invalid",
        "loc": [],
        "msg": "Input tag 'foo' found using 'name' does not match any of the expected tags: 'temperature', 'line_number', 'warning', 'status'",
        "input": {
            "name": "foo",
            "value": "bar"
        },
        "ctx": {
            "discriminator": "'name'",
            "tag": "foo",
            "expected_tags": "'temperature', 'line_number', 'warning', 'status'"
        },
        "url": "https://errors.pydantic.dev/2.1/v/union_tag_invalid"
    }
]

Original Answer: Using Pydantic v1

If you are willing to switch to Pydantic instead of dataclasses, you can define a discriminated union via typing.Annotated and use the parse_obj_as function as a "universal" constructor that is able to discriminate between distinct Event subtypes based on the provided name string.

Here is what I would suggest:

from typing import Annotated, Any, Literal

from pydantic import BaseModel, Field, parse_obj_as


class EventBase(BaseModel):
    name: str
    value: Any


class NumberEvent(EventBase):
    name: Literal["temperature", "line_number"]
    value: float


class StringEvent(EventBase):
    name: Literal["warning", "status"]
    value: str


Event = Annotated[
    NumberEvent | StringEvent,
    Field(discriminator="name"),
]


event_temp = parse_obj_as(Event, {"name": "temperature", "value": "3.14"})
event_status = parse_obj_as(Event, {"name": "status", "value": -10})

print(repr(event_temp))    # NumberEvent(name='temperature', value=3.14)
print(repr(event_status))  # StringEvent(name='status', value='-10')

In this usage demo I purposefully used the "wrong" types for the respective value fields to show that Pydantic will automatically try to coerce them to the right types, once it determines the correct model based on the provided name.

An invalid name would of course cause a validation error, just like a completely wrong and type for value (that cannot be coerced). Example:

from pydantic import ValidationError

try:
    parse_obj_as(Event, {"name": "temperature", "value": "foo"})
except ValidationError as err:
    print(err.json(indent=4))

try:
    parse_obj_as(Event, {"name": "foo", "value": "bar"})
except ValidationError as err:
    print(err.json(indent=4))

Output:

[
    {
        "loc": [
            "__root__",
            "NumberEvent",
            "value"
        ],
        "msg": "value is not a valid float",
        "type": "type_error.float"
    }
]
[
    {
        "loc": [
            "__root__"
        ],
        "msg": "No match for discriminator 'name' and value 'foo' (allowed values: 'temperature', 'line_number', 'warning', 'status')",
        "type": "value_error.discriminated_union.invalid_discriminator",
        "ctx": {
            "discriminator_key": "name",
            "discriminator_value": "foo",
            "allowed_values": "'temperature', 'line_number', 'warning', 'status'"
        }
    }
]

Side notes

An alias for a union of types like NumberEvent | StringEvent should still have a singular name, i.e. Event rather than Events because semantically the annotation e: Event indicates e should be an instance of one of those types, whereas e: Events would suggest e will be multiple instances (a collection) of either of those types.

Also the union float | int is almost always equivalent to float because int is by convention considered a subtype of float by all type checkers.

Top answer
1 of 5
538

Data classes are just regular classes that are geared towards storing state, rather than containing a lot of logic. Every time you create a class that mostly consists of attributes, you make a data class.

What the dataclasses module does is to make it easier to create data classes. It takes care of a lot of boilerplate for you.

This is especially useful when your data class must be hashable; because this requires a __hash__ method as well as an __eq__ method. If you add a custom __repr__ method for ease of debugging, that can become quite verbose:

class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def __init__(
            self, 
            name: str, 
            unit_price: float,
            quantity_on_hand: int = 0
        ) -> None:
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand
    
    def __repr__(self) -> str:
        return (
            'InventoryItem('
            f'name={self.name!r}, unit_price={self.unit_price!r}, '
            f'quantity_on_hand={self.quantity_on_hand!r})'
        )

    def __hash__(self) -> int:
        return hash((self.name, self.unit_price, self.quantity_on_hand))

    def __eq__(self, other) -> bool:
        if not isinstance(other, InventoryItem):
            return NotImplemented
        return (
            (self.name, self.unit_price, self.quantity_on_hand) == 
            (other.name, other.unit_price, other.quantity_on_hand))

With dataclasses you can reduce it to:

from dataclasses import dataclass

@dataclass(unsafe_hash=True)
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

(Example based on the PEP example).

The same class decorator can also generate comparison methods (__lt__, __gt__, etc.) and handle immutability.

namedtuple classes are also data classes, but are immutable by default (as well as being sequences). dataclasses are much more flexible in this regard, and can easily be structured such that they can fill the same role as a namedtuple class.

The PEP was inspired by the attrs project, which can do even more (including slots, validators, converters, metadata, etc.).

If you want to see some examples, I recently used dataclasses for several of my Advent of Code solutions, see the solutions for day 7, day 8, day 11 and day 20.

If you want to use dataclasses module in Python versions < 3.7, then you could install the backported module (requires 3.6) or use the attrs project mentioned above.

2 of 5
277

Overview

The question has been addressed. However, this answer adds some practical examples to aid in the basic understanding of dataclasses.

What exactly are python data classes and when is it best to use them?

  1. code generators: generate boilerplate code; you can choose to implement special methods in a regular class or have a dataclass implement them automatically.
  2. data containers: structures that hold data (e.g. tuples and dicts), often with dotted, attribute access such as classes, namedtuple and others.

"mutable namedtuples with default[s]"

Here is what the latter phrase means:

  • mutable: by default, dataclass attributes can be reassigned. You can optionally make them immutable (see Examples below).
  • namedtuple: you have dotted, attribute access like a namedtuple or a regular class.
  • default: you can assign default values to attributes.

Compared to common classes, you primarily save on typing boilerplate code.


Features

This is an overview of dataclass features (TL;DR? See the Summary Table in the next section).

What you get

Here are features you get by default from dataclasses.

Attributes + Representation + Comparison

import dataclasses


@dataclasses.dataclass
#@dataclasses.dataclass()                                       # alternative
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

These defaults are provided by automatically setting the following keywords to True:

@dataclasses.dataclass(init=True, repr=True, eq=True)

What you can turn on

Additional features are available if the appropriate keywords are set to True.

Order

@dataclasses.dataclass(order=True)
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

The ordering methods are now implemented (overloading operators: < > <= >=), similarly to functools.total_ordering with stronger equality tests.

Hashable, Mutable

@dataclasses.dataclass(unsafe_hash=True)                        # override base `__hash__`
class Color:
    ...

Although the object is potentially mutable (possibly undesired), a hash is implemented.

Hashable, Immutable

@dataclasses.dataclass(frozen=True)                             # `eq=True` (default) to be immutable 
class Color:
    ...

A hash is now implemented and changing the object or assigning to attributes is disallowed.

Overall, the object is hashable if either unsafe_hash=True or frozen=True.

See also the original hashing logic table with more details.

Optimization

@dataclasses.dataclass(slots=True)              # py310+
class SlottedColor:
    #__slots__ = ["r", "b", "g"]                # alternative
    r : int
    g : int
    b : int

The object size is now reduced:

>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888

slots=True was added in Python 3.10. (Thanks @ajskateboarder).

In some circumstances, slots=True/__slots__ also improves the speed of creating instances and accessing attributes. Also, slots do not allow default assignments; otherwise, a ValueError is raised. If __slot__ already exists, slots=True will cause a TypeError.

See more on slots in this blog post.

See more on arguments added in Python 3.10+: match_args, kw_only, slots, weakref_slot.

What you don't get

To get the following features, special methods must be manually implemented:

Unpacking

@dataclasses.dataclass
class Color:
    r : int = 0
    g : int = 0
    b : int = 0

    def __iter__(self):
        yield from dataclasses.astuple(self)

Summary Table

+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
|       Feature        |       Keyword        |                      Example                       |           Implement in a Class          |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes           |  init                |  Color().r -> 0                                    |  __init__                               |
| Representation       |  repr                |  Color() -> Color(r=0, g=0, b=0)                   |  __repr__                               |
| Comparision*         |  eq                  |  Color() == Color(0, 0, 0) -> True                 |  __eq__                                 |
|                      |                      |                                                    |                                         |
| Order                |  order               |  sorted([Color(0, 50, 0), Color()]) -> ...         |  __lt__, __le__, __gt__, __ge__         |
| Hashable             |  unsafe_hash/frozen  |  {Color(), {Color()}} -> {Color(r=0, g=0, b=0)}    |  __hash__                               |
| Immutable            |  frozen + eq         |  Color().r = 10 -> TypeError                       |  __setattr__, __delattr__               |
| Optimization         |  slots               |  sys.getsizeof(SlottedColor) -> 888                |  __slots__                              |
|                      |                      |                                                    |                                         |
| Unpacking+           |  -                   |  r, g, b = Color()                                 |  __iter__                               |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+

* __ne__ is not needed and thus not implemented.

+These methods are not automatically generated and require manual implementation in a dataclass.


Additional features

Post-initialization

@dataclasses.dataclass
class RGBA:
    r : int = 0
    g : int = 0
    b : int = 0
    a : float = 1.0

    def __post_init__(self):
        self.a : int =  int(self.a * 255)


RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)

Inheritance

@dataclasses.dataclass
class RGBA(Color):
    a : int = 0

Conversions

Convert a dataclass to a tuple or a dict, recursively:

>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{'r': 128, 'g': 0, 'b': 255}

Limitations

  • Lacks mechanisms to handle starred arguments
  • Working with nested dataclasses can be complicated

References

  • R. Hettinger's talk on Dataclasses: The code generator to end all code generators
  • T. Hunner's talk on Easier Classes: Python Classes Without All the Cruft
  • Python's documentation on hashing details
  • Real Python's guide on The Ultimate Guide to Data Classes in Python 3.7
  • A. Shaw's blog post on A brief tour of Python 3.7 data classes
  • E. Smith's github repository on dataclasses
๐ŸŒ
DataCamp
datacamp.com โ€บ tutorial โ€บ python-data-classes
Python Data Classes: A Comprehensive Tutorial | DataCamp
March 15, 2024 - Letโ€™s cover some of the fundamental concepts of Python data classes that make the so useful. Despite all their features, data classes are regular classes that take much less code to implement the same functionality. Here is the Exercise class again: from dataclasses import dataclass @dataclass class Exercise: name: str reps: int sets: int weight: float ex1 = Exercise("Bench press", 10, 3, 52.5) # Verifying Exercise is a regular class ex1.name 'Bench press'
๐ŸŒ
Mypy
mypy.readthedocs.io โ€บ en โ€บ stable โ€บ additional_features.html
Additional features - mypy 1.20.0 documentation
Mypy will detect special methods (such as __lt__) depending on the flags used to define dataclasses.
๐ŸŒ
Ritviknag
dcw.ritviknag.com โ€บ en โ€บ latest
Why Dataclass Wizard? โ€” Dataclass Wizard 0.39.1 documentation
from __future__ import annotations # Remove in Python 3.10+ from dataclasses import dataclass from typing import Literal from dataclass_wizard import JSONWizard @dataclass class MyClass(JSONWizard): class _(JSONWizard.Meta): v1 = True # Enable v1 opt-in v1_unsafe_parse_dataclass_in_union = True literal_or_float: Literal['Auto'] | float entry: int | MoreDetails collection: list[MoreDetails | int] @dataclass class MoreDetails: arg: str # OK: Union types work seamlessly c = MyClass.from_dict({ "literal_or_float": 1.23, "entry": 123, "collection": [{"arg": "test"}] }) print(repr(c)) #> MyClass(lit
๐ŸŒ
Medium
nuung.medium.com โ€บ python-pythonic-dataclass-and-enum-from-real-world-practice-b6a1a22ebb7c
Python โ€” Pythonic dataclass and ENUM (from real-world practice) | by Nuung | Medium
June 18, 2025 - For external API call response value restriction, you can use it freely with pydantic & dataclass, and you can define specific values most easily and quickly in function parameters, so personally I still use Literal well. ps) Throughout Python 3.9~3.11, some features of the typing library became ...