Literal text refers to a sequence of characters exactly as they appear, enclosed in quotes, and interpreted exactly as written in programming or documentation.
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 combiningLiteral
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.
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.
mypy
Before diving into Literal
, ensure we have mypy
installed. We can install it using pip
:
pip install mypy
The following command is used to run mypy
on code:
mypy file-name.py
Consider an example where we define a function to make HTTP requests. The method parameter should only accept specific HTTP methods.
from typing import Literaldef 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 workmake_request('POST', 'https://example.com') # This will workmake_request('PATCH', 'https://example.com') # This will raise an error during type checking
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.
Literal
with enumsThe Literal
works well with
from enum import Enumfrom typing import Literalclass 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 workprint(get_status(Status.CLOSED)) # This will workprint(get_status('in_progress')) # This will raise an error during type checking
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.
Literal
with other types using UnionWe can combine Literal
with other types using Union.
from typing import Union, Literaldef 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 workprint(get_status(404)) # This will workprint(get_status('error')) # This will raise an error during type checking
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'
).
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.
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.
Haven’t found what you were looking for? Contact Us
Free Resources