How to use Python typing Literal

Key takeaways:

  • Python's Literal type hint allows specifying exact valid values for variables or function parameters to improve type safety.

  • It works with static type checkers like mypy but doesn't enforce constraints at runtime.

  • Enums can be combined with Literal to define valid status values more clearly.

  • Union enables combining Literal with other types for greater flexibility.

  • Limitations include no runtime enforcement, verbosity for large sets, and inability to represent dynamic or complex relationships.

  • Using Literal enhances code clarity, reduces bugs, and ensures maintainable type hints.

Python, as a dynamically typed language, offers great flexibility but can sometimes lead to runtime errors, such as a null reference or attribute error, that are hard to debug. To counter this, Python introduced type hints, allowing developers to specify the types of variables, function arguments, and return values. One of the powerful features of Python's type hints module is Literal, which enables more precise type checking. In this Answer, we’ll explore how to use Python's Literal with the static type checker mypy with executable examples.

What is Literal?

The Literal is a type hint that allows us to specify that a variable or function parameter can only take on specific values. This is particularly useful for function parameters that should only accept a fixed set of known values.

Setting up mypy

Before diving into Literal, ensure we have mypy installed. We can install it using pip:

pip install mypy
Command to install mypy

The following command is used to run mypy on code:

mypy file-name.py
Command to run code using mypy

Practical examples

Consider an example where we define a function to make HTTP requests. The method parameter should only accept specific HTTP methods.

from typing import Literal
def make_request(method: Literal['GET', 'POST', 'PUT', 'DELETE'], url: str) -> None:
print(f"Making a {method} request to {url}")
make_request('GET', 'https://example.com') # This will work
make_request('POST', 'https://example.com') # This will work
make_request('PATCH', 'https://example.com') # This will raise an error during type checking

Explanation

  • Lines 3–4: The make_request function is designed with two parameters: method, annotated with Literal['GET', 'POST', 'PUT', 'DELETE'], restricting it to only accept specific HTTP methods (GET, POST, PUT, or DELETE), and url, a standard string (str) representing the request destination. The function has a return type -> None, indicating it doesn’t return a value but rather prints a formatted message in its single-line implementation within the function body. This message specifies the HTTP method and the corresponding URL for the request.

  • Lines 6–8: The make_request function accepts specific HTTP methods ('GET', 'POST', 'PUT', 'DELETE') and a URL parameter. Calls with 'GET' or 'POST' will succeed, but using 'PATCH' will raise a type error during type checking (mypy), indicating that 'PATCH' is not a valid method according to the specified constraints.

Using Literal with enums

The Literal works well with enumsEnums in Python are a way to define named constant values, improving code readability and maintainability., allowing us to define a set of string literals that correspond to enum values.

from enum import Enum
from typing import Literal
class Status(Enum):
OPEN = 'open'
CLOSED = 'closed'
def get_status(status: Literal[Status.OPEN, Status.CLOSED]) -> str:
return f"The status is {status.value}"
print(get_status(Status.OPEN)) # This will work
print(get_status(Status.CLOSED)) # This will work
print(get_status('in_progress')) # This will raise an error during type checking

Explanation

  • Lines 4–6: These define the Status enum with two members, OPEN and CLOSED, each representing a string value ('open' and 'closed').

  • Lines 8–9: These define the get_status function, which takes a status parameter restricted to Status.OPEN or Status.CLOSED using Literal. The function returns a string indicating the current status.

  • Lines 11–13: These calls to get_status with Status.OPEN and Status.CLOSED work correctly, printing the corresponding status messages. A call with 'in_progress' will raise a type error during type checking because it is not a valid status value.

Combining Literal with other types using Union

We can combine Literal with other types using Union.

from typing import Union, Literal
def get_status(status: Union[Literal['open', 'closed'], int]) -> str:
if isinstance(status, int):
return f"Status code: {status}"
return f"The status is {status}"
print(get_status('open')) # This will work
print(get_status(404)) # This will work
print(get_status('error')) # This will raise an error during type checking

Explanation

  • Lines 3–6: The get_status function is designed to accept a status parameter, which can be either a string literal ('open' or 'closed') or an integer, as indicated by the Union[Literal['open', 'closed'], int] type hint. The function returns a string. Inside the get_status function, it checks if the status parameter is an integer using isinstance. If status is an integer, the function returns a formatted string “Status code: {status}”. If status is a string literal, the function returns "The status is {status}".

  • Lines 6-8: These lines demonstrate calls to the get_status function:

    • The get_status('open') works correctly, printing “The status is open” because 'open' is a valid literal according to the type hint.

    • The get_status(404) works correctly, printing “Status code: 404” because 404 is an integer.

    • The get_status('error') will raise a type error during static type checking, because 'error' is not included in the specified literals ('open' or 'closed').

Limitations of typing literals

Some limitations associated with typing literals in Python are:

  • No runtime enforcement: Type hints, including Literal, are only checked by static type checkers (e.g., mypy) and have no effect at runtime. Invalid values passed to a Literal-annotated parameter will not raise errors unless explicitly handled in the code.

  • Limited expressiveness: Literal is best suited for simple values like strings, numbers, or booleans. It cannot easily express complex conditions or relationships between values.

  • Cannot represent dynamic sets: Literal requires fixed, predefined values. If the valid options are dynamically generated or based on runtime conditions, Literal is not applicable.

  • Verbosity for large sets: When there are many possible values, using Literal can make the code verbose and less maintainable.

Conclusion

Using Literal with mypy allows us to define more precise and expressive type hints in our Python code. This can lead to fewer bugs and clearer, more maintainable code. By following the examples in this Answer, we can start leveraging the power of Literal in our own projects.

Frequently asked questions

Haven’t found what you were looking for? Contact Us


What is literal text?

Literal text refers to a sequence of characters exactly as they appear, enclosed in quotes, and interpreted exactly as written in programming or documentation.


How do you use literal string?

A literal string in Python is used by directly assigning a sequence of characters enclosed in single or double quotes to a variable or passing it as an argument to a function.


How is string literal represented in Python?

In Python, a string literal is represented by enclosing text in either single quotes (') or double quotes (").


Free Resources

Copyright ©2025 Educative, Inc. All rights reserved