我想在我的类型定义中使用“常量”变量,如下所示:
FOO = "foo"
BAR = "bar"
@dataclass
class Event():
name: Literal[FOO, BAR]
但是对于 mypy 来说这是非法代码。
这可行,但是我无法在代码中使用变量:
FOO = Literal["foo"]
BAR = Literal["bar"]
@dataclass
class Event():
name: FOO | BAR
Event(FOO) # gives: Event(name=typing.Literal['foo'])
有没有办法让它工作而无需定义 FOO 和 BAR 两次?
Impelenting Marks 答案(https://stackoverflow.com/a/76382060/5386216)有效,但是 mypy 无法根据事件名称区分类型:
class FooBar(Enum):
FOO = "foo"
BAR = "bar"
@dataclass
class StringEvent:
name: Literal[FooBar.FOO]
value: str
@dataclass
class NumberEvent:
name: Literal[FooBar.BAR]
value: int
def handle_event(event: StringEvent | NumberEvent):
if event.name == FooBar.FOO:
event.value.upper() # should not give a mypy error
if event.name == FooBar.BAR:
event.value.upper() # Should give a mypy error
event.value.upper()
的两种情况都会给出以下 mypy 错误:Item "int" of "Union[str, int]" has no attribute "upper"
问题是类型和值不一样。一种是
Literal
类型,值为字符串。
就像@STerliakov在评论中提到的那样,最好的方法是颠倒过来,首先定义类型,然后根据类型定义常量字符串,如下所示:
from typing import Literal, get_args
FOO_TYPE = Literal["foo"]
FOO = get_args(FOO_TYPE)[0]
BAR_TYPE = Literal["bar"]
BAR = get_args(BAR_TYPE)[0]
这比我想要的要冗长一些,而且不太优雅,但至少你不会重复自己。人们也可以将其放入一个类中并编写一个装饰器来自动执行此操作:
from typing import Literal
def const_literals(cls):
# make a copy of vars because we change the dictionary during iteration
for const, lit in dict(**vars(c)).items():
setattr(cls, f"{const}_TYPE", Literal[lit])
return cls
@const_literals
class Constants:
FOO = "foo"
BAR = "bar"
print(Constants.FOO) # prints "foo"
print(Constants.FOO_TYPE) # prints "typing.Literal['foo']"