如何从typing
Literal[]
中获取文字值?
from typing import Literal, Union
Add = Literal['add']
Multiply = Literal['mul']
Action = Union[Add,Multiply]
def do(a: Action):
if a == Add:
print("Adding!")
elif a == Multiply:
print("Multiplying!")
else:
raise ValueError
do('add')
上面的代码进行类型检查,因为'add'
是Literal['add']
类型,但在运行时,它会引发 ValueError,因为字符串'add'
与typing.Literal['add']
不同。
如何在运行时重用我在类型级别定义的文本?
typing
模块提供了一个函数get_args
,用于检索初始化Literal
时使用的参数。
>>> from typing import Literal, get_args
>>> l = Literal['add', 'mul']
>>> get_args(l)
('add', 'mul')
但是,我认为您对您的建议使用Literal
不会有任何好处。对我来说更有意义的是使用字符串本身,然后可能定义一个Literal
,以验证参数是否属于这组字符串的非常严格的目的。
>>> def my_multiply(*args):
... print("Multiplying {0}!".format(args))
...
>>> def my_add(*args):
... print("Adding {0}!".format(args))
...
>>> op = {'mul': my_multiply, 'add': my_add}
>>> def do(action: Literal[list(op.keys())]):
... return op[action]
请记住,类型批注本质上是一个专用的类型定义,而不是一个值。它限制允许通过哪些值,但它本身只是实现一个约束 - 一个拒绝你不想允许的值的过滤器。 如上所示,它的参数是一组允许的值,因此约束本身仅指定它将接受哪些值,但实际值仅在您具体使用它来验证值时才会出现。
我猜从类型中获取值的愿望是为了避免代码重复,并实现更广泛的重构。 但是让我们考虑一下...
让我们考虑代码重复。 我们不希望必须两次写入相同的文本值。 但事情是这样的,我们将不得不写下两次一些东西,无论是类型还是文字,那么为什么不写字面呢?
让我们考虑启用重构。 在这种情况下,我们担心如果我们更改类型的文字值,那么使用现有值的代码将不再起作用,如果我们能一次更改它们,那就太好了。 请注意,类型检查器解决的问题与此相邻:当您更改该值时,它将在任何地方警告您该值不再有效。 在这种情况下,您可以选择使用Enum
将文本值放入Literal
类型中:
from typing import Literal, overload
from enum import Enum
class E(Enum):
opt1 = 'opt1'
opt2 = 'opt2'
@overload
def f(x: Literal[E.opt1]) -> str:
...
@overload
def f(x: Literal[E.opt2]) -> int:
...
def f(x: E):
if x == E.opt1:
return 'got 0'
elif x == E.opt2:
return 123
raise ValueError(x)
a = f(E.opt1)
b = f(E.opt2)
reveal_type(a)
reveal_type(b)
# > mypy .tmp.py
# tmp.py:28: note: Revealed type is "builtins.str"
# tmp.py:29: note: Revealed type is "builtins.int"
# Success: no issues found in 1 source file
现在,当我想更改E.opt1
的"值"时,其他人甚至都不在乎,而当我想更改E.opt1
的"名称"以E.opt11
重构工具时,它将为我到处都可以。 这样做的"主要问题"是它将要求用户使用Enum,而整个要点是试图提供一个方便的,基于值但类型安全的界面,对吧? 请考虑以下enum
-less代码:
from typing import Literal, overload, get_args
from enum import Enum
TOpt1 = Literal['opt1']
@overload
def f(x: TOpt1) -> str:
...
@overload
def f(x: Literal['opt2']) -> int:
...
def f(x):
if x == get_args(TOpt1):
return 'got 0'
elif x == 'opt2':
return 123
raise ValueError(x)
a = f('opt1')
b = f('opt2')
reveal_type(a)
reveal_type(b)
# > mypy .tmp.py
# tmp.py:24: note: Revealed type is "builtins.str"
# tmp.py:25: note: Revealed type is "builtins.int"
我将两种检查参数值的样式都放在那里:def f(x: TOpt1)
和if x == get_args(TOpt1)
vsdef f(x: Literal['opt2'])
和elif x == 'opt2'
。 虽然第一种风格在某种抽象意义上是"更好的",但我不会这样写,除非TOpt1
出现在多个地方(多个重载或不同的功能(。 如果它只是在一个函数中用于一个重载,那么我绝对会直接使用这些值,而不是打扰get_args
和定义类型别名,因为在f
的实际定义中,我宁愿看一个值而不是想知道类型参数。