问题
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?
回答1:
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))
回答2:
Here is the workaround for this. But don't know if it is a good solution.
VALID_ARGUMENTS = ['foo', 'bar']
Argument = Literal['1']
Argument.__args__ = tuple(VALID_ARGUMENTS)
print(Argument)
# typing.Literal['foo', 'bar']
来源:https://stackoverflow.com/questions/64522040/python-typing-dynamically-create-literal-alias-from-list-of-valid-values