I have a function which validates its argument to accept only values from a given list of valid options. Typing-wise, I reflect this behavior using a Literal
type alias, like so:
from typing import Literal
VALID_ARGUMENTS = ['foo', 'bar']
Argument = Literal['foo', 'bar']
def func(argument: 'Argument') -> None:
if argument not in VALID_ARGUMENTS:
raise ValueError(
f'argument must be one of {VALID_ARGUMENTS}'
)
# ...
This is a violation of the DRY principle, because I have to rewrite the list of valid arguments in the definition of my Literal type, even if it is already stored in the variable VALID_ARGUMENTS
. How can I create the Argument
Literal type dynamically, given the VALID_ARGUMENTS
variable?
The following things do not work:
from typing import Literal, Union, NewType
Argument = Literal[*VALID_ARGUMENTS] # SyntaxError: invalid syntax
Argument = Literal[VALID_ARGUMENTS] # Parameters to generic types must be types
Argument = Literal[Union[VALID_ARGUMENTS]] # TypeError: Union[arg, ...]: each arg must be a type. Got ['foo', 'bar'].
Argument = NewType(
'Argument',
Union[
Literal[valid_argument]
for valid_argument in VALID_ARGUMENTS
]
) # Expected type 'Type[_T]', got 'list' instead
So, how can it be done? Or can't it be done at all?
A type alias is defined by assigning the type to the alias. In this example, Vector and list [float] will be treated as interchangeable synonyms: Type aliases are useful for simplifying complex type signatures.
From the mypy documentation: " Literal types may contain one or more literal bools, ints, strs, bytes, and enum values. However, literal types cannot contain arbitrary expressions: types like Literal [my_string.trim ()], Literal [x > 3], or Literal [3j + 4] are all illegal."
These type aliases correspond to the return types from re.compile () and re.match (). These types (and the corresponding functions) are generic in AnyStr and can be made specific by writing Pattern [str], Pattern [bytes], Match [str], or Match [bytes].
Use Any to indicate that a value is dynamically typed. Initially PEP 484 defined Python static type system as using nominal subtyping. This means that a class A is allowed where a class B is expected if and only if A is a subclass of B.
Go the other way around, and build VALID_ARGUMENTS
from Argument
:
Argument = typing.Literal['foo', 'bar']
VALID_ARGUMENTS: typing.Tuple[Argument, ...] = typing.get_args(Argument)
It's possible at runtime to build Argument
from VALID_ARGUMENTS
, but doing so is incompatible with static analysis, which is the primary use case of type annotations. Building VALID_ARGUMENTS
from Argument
is the way to go.
I've used a tuple for VALID_ARGUMENTS
here, but if for some reason you really prefer a list, you can get one:
VALID_ARGUMENTS: typing.List[Argument] = list(typing.get_args(Argument))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With