I’d guess age and momentum? I don’t think enums were used nearly as much before type hinting was more common. Answer from wyldstallionesquire on reddit.com
Top answer
1 of 2
16

I'm afraid there's no such way. The first thing that comes to mind is iterating over enum's values to build a Literal type won't work, because Literal cannot contain arbitrary expressions. So, you cannot specify it explicitly:

# THIS DOES NOT WORK
def is_enabled(state: State | Literal[State.ENABLED.value, State.DISABLED.value]) -> bool:
    ...

There's an open issue on GitHub with the related discussion. Basically, you could have hardcoded literals for every Enum, but in that case, you need to update it in accordance with Enum updates, and one day it will be messy. So, I would either stick with State | str annotation or just State and expect your function to accept only enums.

Also, take into account that you do not need to explicitly create an Enum object to test its value, you can just write "enabled" == State.ENABLED as I mentioned in the comments.

2 of 2
1

It's very annoying that you can't easily create a Literal type hint for all the members of an enum, but one potential workaround if you have control of the enum (and can programmatically determine what the member name should be from the value) is to reverse the direction

Instead of creating a Literal type hint from the enum members, you can create an enum from the values in a Literal type hint using the functional API for Enum and typing.get_args

Your example would look like:

from enum import Enum
from typing import Literal, get_args

# variable naming might not be great here,
# but I figure it's good enough for the short example
StateValues = Literal["enabled", "disabled"]
State = Enum(
    "State",
    ((state.upper(), state) for state in get_args(StateValues))
    module="...",  # fill in module and qualname to help with pickling
    qualname="...",
)

def is_enabled(state: State | StateValues) -> bool:
    return state is State.ENABLED or state == State.ENABLED.value

Huge downside to this approach is that now static analysis tools will flag State.ENABLED as an unknown attribute (and you lose the ability to put individual enum members in type hints or do exhaustiveness checking on enum members)

The functional API won't let you do everything that the class API does, but if all you need are the members to be defined, then this should work


Overall, this approach probably isn't worth it

Discussions

Literal of enum values
I am trying to implement a function that, as the type of one argument, can accept either an Enum or a value of such an enum (because some Python programmers are more used to type strings instead of enum values). Currently I have managed ... More on github.com
🌐 github.com
10
January 21, 2021
Python, what's the Enum type good for? - Stack Overflow
Appreciate this comprehensive answer greatly. It is seeming to me that one could now replace (entirely?) Enums with Literal types python.org/dev/peps/pep-0586 - thoughts on this? More on stackoverflow.com
🌐 stackoverflow.com
Amend PEP 586 to make `enum` values subtypes of `Literal` - Typing - Discussions on Python.org
Inspired by this highly upvoted issue: Literal of enum values · Issue #781 · python/typing · GitHub, I propose that enum values, if they are defined literally and are instances of the type of the literal (So this would be applicable mostly to IntEnum and StrEnum, but not regular Enum) Then ... More on discuss.python.org
🌐 discuss.python.org
11
July 29, 2024
Which is better: string, or enum-like global constants in interface?
I would choose enum over str when you have multiple options. 1.you can use type hint with enum 2.You can map enum to str (see my comment) 3.you can use enum utitilies and decorators such as @unique 4.You can loop over enum and it will return iterator of all the members 5.its a closed group that easy to maintain This is just from the top of my head More on reddit.com
🌐 r/learnpython
6
5
February 3, 2024
🌐
Mypy
mypy.readthedocs.io › en › stable › literal_types.html
Literal types and Enums - mypy 1.19.1 documentation
The examples in this page import Literal as well as Final and TypedDict from the typing module. These types were added to typing in Python 3.8, but are also available for use in Python 3.4 - 3.7 via the typing_extensions package. Literal types may contain one or more literal bools, ints, strs, bytes, and enum values.
🌐
GitHub
github.com › python › typing › issues › 781
Literal of enum values · Issue #781 · python/typing
January 21, 2021 - I am trying to implement a function that, as the type of one argument, can accept either an Enum or a value of such an enum (because some Python programmers are more used to type strings instead of enum values). Currently I have managed to do the following: class LinkageCriterion(enum.Enum): WARD = "ward" COMPLETE = "complete" AVERAGE = "average" SINGLE = "single" LinkageCriterionLike = Union[ LinkageCriterion, Literal["ward", "complete", "average", "single"] ]
Author   vnmabus
Top answer
1 of 1
269

What's the purpose of enums? What value do they create for the language? When should I use them and when should I avoid them?

The Enum type got into Python via PEP 435. The reasoning given is:

The properties of an enumeration are useful for defining an immutable, related set of constant values that may or may not have a semantic meaning.

When using numbers and strings for this purpose, they could be characterized as "magic numbers" or "magic strings". Numbers rarely carry with them the semantics, and strings are easily confused (capitalization? spelling? snake or camel-case?)

Days of the week and school letter grades are examples of this kind of collections of values.

Here's an example from the docs:

from enum import Enum

class Color(Enum):
    red = 1
    green = 2
    blue = 3

Like the bare class, this is much more readable and elegant than the namedtuple example, it is also immutable, and it has further benefits as we'll see below.

Strictly dominant: The type of the enum member is the enum

>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True

This allows you to define functionality on the members in the Enum definition. Defining functionality on the values could be accomplished with the other prior methods, but it would be very inelegant.

Improvement: String coercion

The string representation is human readable, while the repr has more information:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

I find this to be an improvement over the magic numbers and even possibly better than strings from the namedtuple.

Iteration (parity):

The enum supports iteration (like the namedtuple, but not so much the bare class) too:

>>> for color in Color:
        print(color)
Color.red
Color.green
Color.blue

The __members__ attribute is an ordered mapping of the names of the enums to their respective enum objects (similar to namedtuple's _asdict() function).

>>> Color.__members__
mappingproxy(OrderedDict([('red', <Color.red: 1>), ('green', <Color.green: 2>), 
('blue', <Color.blue: 3>)]))

Supported by pickle (parity)

You can serialize and deserialize the enum (in case anyone was worried about this):

>>> import pickle
>>> color.red is pickle.loads(pickle.dumps(color.red))
True

Improvement: Aliases

This is a nice feature that the bare class doesn't have, and it would be difficult to tell the alias was there in the namedtuple.

class Color(Enum):
    red = 1
    green = 2
    blue = 3
    really_blue = 3

The alias comes after the canonical name, but they are both the same:

>>> Color.blue is Color.really_blue
True

If aliases should be prohibited to avoid value collisions, use the enum.unique decorator (a strictly dominant feature).

Strictly dominant: comparisons done with is

The enum is intended to be tested with is, which is a fast check for a single object's identity in the process.

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

Tests for equality work as well, but tests for identity with is are optimal.

Different semantics from other Python classes

Enum classes have different semantics from regular Python types. The values of the Enum are instances of the Enum, and are singletons in memory for those values - there is no other purpose for instantiating them.

>>> Color.red is Color(1)

This is important to keep in mind, perhaps it is a downside, but comparing on this dimension is comparing apples with oranges.

Enums not assumed to be ordered

While the Enum class knows what order the members are created in, enums are not assumed to be ordered. This is a feature because many things that may be enumerated have no natural order, and therefore order would be arbitrary.

However, you can give your enums order (see the next section).

Subclassing

You can't subclass an Enum with members declared, but you can subclass an Enum that doesn't declare members to share behavior (see the OrderedEnum recipe in the docs).

This is a feature - it makes little sense to subclass an Enum with members, but again, the comparison is apples and oranges.

When should I use enum.Enum?

This is the new canonical enumeration in Python. Collaborators will expect your enums to behave like these enums.

Use it anywhere you have a canonical source of enumerated data in your code where you want explicitly specified to use the canonical name, instead of arbitrary data.

For example, if in your code you want users to state that it's not "Green", "green", 2, or "Greene", but Color.green - use the enum.Enum object. It's both explicit and specific.

There are a lot of examples and recipes in the documentation.

When should I avoid them?

Stop rolling your own or letting people guess about magic numbers and strings. Don't avoid them. Embrace them.

However, if your enum members are required to be integers for historic reasons, there's the IntEnum from the same module, which has the same behavior, but is also an integer because it subclasses the builtin int before subclassing Enum. From IntEnum's help:

class IntEnum(builtins.int, Enum)

we can see that the IntEnum values would test as an instance of an int.

🌐
Towards Data Science
towardsdatascience.com › home › latest › python type hinting with literal
Python Type Hinting with Literal | Towards Data Science
January 22, 2025 - Comparison of enum.Enum and typing.Literal. Image by author · Despite their versatility, literal types excel in simplicity, brevity, and readability. While Python enumerations are also straightforward and readable, literal types offer an even higher level of clarity and conciseness.
Find elsewhere
🌐
Python.org
discuss.python.org › typing
Amend PEP 586 to make `enum` values subtypes of `Literal` - Typing - Discussions on Python.org
July 29, 2024 - Inspired by this highly upvoted issue: Literal of enum values · Issue #781 · python/typing · GitHub, I propose that enum values, if they are defined literally and are instances of the type of the literal (So this would be applicable mostly to IntEnum and StrEnum, but not regular Enum) Then they should be subtypes of the corresponding literal type.
🌐
Python
typing.python.org › en › latest › spec › literal.html
Literals — typing documentation
Literal may be parameterized with literal int, str, bytes, and bool objects, instances of enum.Enum subclasses, and None.
🌐
YouTube
youtube.com › watch
python enum vs literal
Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.
🌐
Python documentation
docs.python.org › 3 › howto › enum.html
Enum HOWTO — Python 3.14.3 documentation
Unlike many languages that treat enumerations solely as name/value pairs, Python Enums can have behavior added. For example, datetime.date has two methods for returning the weekday: weekday() and isoweekday(). The difference is that one of them counts from 0-6 and the other from 1-7.
🌐
Python
typing.python.org › en › latest › spec › enums.html
Enumerations — typing documentation
From the perspective of the type system, most enum classes are equivalent to the union of the literal members within that enum.
🌐
Python
peps.python.org › pep-0586
PEP 586 – Literal Types - Python Enhancement Proposals
March 14, 2019 - Some of this complexity will be alleviated once Literal types are introduced: rather than entirely special-casing enums, we can instead treat them as being approximately equivalent to the union of their values and take advantage of any existing ...
🌐
Reddit
reddit.com › r/learnpython › which is better: string, or enum-like global constants in interface?
r/learnpython on Reddit: Which is better: string, or enum-like global constants in interface?
February 3, 2024 -

Contrived example. Suppose you are defining the API of a class FireExtinguisher that takes a constructor argument specifying whether it's type A, B or C. Which interface is better:

import firefighting as ff

extinguisher = ff.FireExtinguisher(ff.C)

# or

extinguisher = ff.FireExtinguisher("C")

or something else?

It's easy to find instances of the first way: for example, the logging module defines logging.DEBUG, logging.INFO, etc. Similarly, the subprocess module defines several constants for Windows. But the string approach looks more concise.

In C, one would argue that the enum style is more efficient and provides a compile time check that you haven't specified an invalid name, while the string version would require a run-time check for validity. But in python, it's a runtime check, anyway, right?

What is the best way to do this in python and why?

🌐
GitHub
github.com › sphinx-doc › sphinx › issues › 11473
Enum values inside `typing.Literal` render weirdly · Issue #11473 · sphinx-doc/sphinx
June 25, 2023 - For some reason, it shows ~typing.Literal, rather than just Literal, and the enum variant seems to just be the output of repr(SomeEnum.X), which includes the value of that enum, which isn't something we'd generally need to see.
Author   ItsDrike
🌐
GitHub
github.com › python › mypy › issues › 8657
Enum member aliases does not work correctly with Literal · Issue #8657 · python/mypy
July 23, 2025 - The bigger problem comes, when we want to use Literal on such aliases: import enum from typing import Literal, Optional class Color(enum.Enum): BLACK = enum.auto() BLACK = Color.BLACK BLACK_ALIAS: Literal[Color.BLACK] = Color.BLACK x: Optional[Literal[Color.BLACK]] = None y: Optional[Literal[BLACK]] = None z: Optional[Literal[BLACK_ALIAS]] = None reveal_type(x) reveal_type(y) reveal_type(z)
Author   kprzybyla
🌐
Hacker News
news.ycombinator.com › item
Using Enumerated Types in Python | Hacker News
June 8, 2020 - https://github.com/python/mypy/issues/6366 · https://github.com/python/mypy/issues/5818
🌐
Python
docs.python.org › 3 › library › enum.html
enum — Support for enumerations
1 month ago - Even though we can use class syntax to create Enums, Enums are not normal Python classes. See How are Enums different?
Top answer
1 of 1
2

Is there a construct as simple as Enum, but to make types instead of values?

Yes, the metaclass. A metaclass makes types. It is simple in terms of usage i.e. creation of new types, but you do need to put in some more work to set it up properly.

Semantically, you could think of the Dimension is a type and Time, Distance etc. as instances of it. In other words the type of the Time class is Dimension. This seems to reflect your view since you said:

I want type(Time) to be Dim

Now a Quantity could be considered the abstract base class of type Dimension. Something without a symbol.

Time would inherit from Quantity (thus also being of type Dimension). No generics so needed so far.

Now you can define a Container that is generic in terms of the type(s) of quantity (i.e. instances of Dimension) it holds.

The metaclass and base class could look like this:

from __future__ import annotations
from typing import Any, ClassVar, TypeVar, overload

T = TypeVar("T", bound=type)


class Dimension(type):
    _types_registered: ClassVar[dict[str, Dimension]] = {}

    @overload
    def __new__(mcs, o: object, /) -> Dimension: ...

    @overload
    def __new__(
        mcs: type[T],
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, Any],
        /,
        **kwargs: Any,
    ) -> T: ...

    def __new__(
        mcs,
        name: Any,
        bases: Any = None,
        namespace: Any = None,
        /,
        **kwargs: Any,
    ) -> type:
        if bases is None and namespace is None:
            return mcs._types_registered[name]
        symbol = kwargs.pop("symbol", None)
        dim = super().__new__(mcs, name, bases, namespace, **kwargs)
        if symbol is not None:
            mcs._types_registered[symbol] = dim
        return dim


class Quantity(metaclass=Dimension):  # abstract base (no symbol)
    pass

And to create new Dimension classes you just inherit from Quantity:

from typing import Generic, TypeVar, reveal_type

# ... import Quantity


class Time(Quantity, symbol="t"):
    pass


DimTime = Dimension("t")
print(DimTime)        # <class '__main__.Time'>
print(type(Time))     # <class '__main__.Dimension'>
reveal_type(DimTime)  # mypy note: Revealed type is "Dimension"


Q = TypeVar("Q", bound=Quantity)


class Container(Generic[Q]):
    """generic container for `Dimension` instances (i.e. quantities)"""
    some_quantity: Q

I realize this completely bypasses your Enum question, but since you even phrased the question as an XY Problem yourself by explaining what your actual intent was, I thought I'd give it a go and suggest a different approach.

🌐
Python.org
discuss.python.org › typing
Inference on Literal Types - Page 2 - Typing - Discussions on Python.org
November 25, 2024 - Here’s another example, contrasting the behavior of mypy and pyright with Enum and Literal. From a typing perspective, my colleagues and I like to view Literal as a typing shorthand for creating an Enum class, but pyright makes this hard to do because of the type “widening” going on. from enum import Enum from typing import Literal class MyEnumAB(Enum): A = 1 B = 2 def enum_returnset(thelist: list[MyEnumAB]) -> set[MyEnumAB]: y = set([x for x in thelist]) reveal_type(y) ...