我想编写一个 mypy 插件,以便为
NotRequired[Optional[T]]
引入类型别名。 (正如我在
这个问题
中发现的,不可能用普通的python编写这个类型别名,因为
NotRequired
在
TypedDict
定义之外不允许使用。)
我的想法是定义一个通用
Possibly
类型,如下所示:
# possibly.__init__.py
from typing import Generic, TypeVar
T = TypeVar("T")
class Possibly(Generic[T]):
pass
然后我希望我的插件将任何出现的
Possibly[X]
替换为
NotRequired[Optional[X]]
我尝试了以下方法:
# possibly.plugin
from mypy.plugin import Plugin
class PossiblyPlugin(Plugin):
def get_type_analyze_hook(self, fullname: str):
if fullname != "possibly.Possibly":
return
return self._replace_possibly
def _replace_possibly(self, ctx):
arguments = ctx.type.args
breakpoint()
def plugin(version):
return PossiblyPlugin
在断点处,我知道我必须基于
mypy.types.Type
构造
arguments
的子类的实例。但我没有找到构造
NotRequired
的方法。
mypy.types
中没有对应的类型。我认为这可能是因为
typing.NotRequired
不是一个类,而是一个
typing._SpecialForm
(我猜这是因为
NotRequired
不影响值类型,但是
.__optional_keys__
的
TypedDict
的定义发生在
上。)
TypedDict
所以,然后我想到了一个不同的策略:我可以检查对于
Possibly
,查看哪些字段被标记为
.__optional_keys__
,并设置
TypedDict
实例的
Possibly
,使该字段不需要,并将
mypy.types.UnionType(*arguments, None)
类型替换为
mypy.plugin.Plugin
但我没有找到
TypedDict
使用哪种方法才能将
放入上下文中。
mypy
所以,我陷入了困境。这是我第一次深入
你是在正确的轨道上,但不幸的是,Mypy 目前没有提供直接的方式在插件中生成
NotRequired
类型或修改
__optional_keys__
。
NotRequired
的特殊性质和它与
TypedDict
的绑定使得在其他上下文中使用它变得很棘手。
探索其他选择:
-
运行时行为:
由于你不能直接在类型级别上实现你想要的东西,所以请考虑你的目标是什么以及你是否可以通过运行时行为来实现它。例如,你可以创建一个接受
Possibly[T]
的函数,并在内部将None
值视为缺少的键。
```python from typing import TypeVar, Optional
T = TypeVar('T')
def process_data(data: dict[str, Possibly[T]]): for key, value in data.items(): if isinstance(value, Possibly): real_value: Optional[T] = getattr(value, 'value', None) else: real_value = value # 处理 real_value,它现在是 Optional[T] 类型 ```
-
特征请求: 如果你需要更强大的
NotRequired
支持,可以考虑在 Mypy 存储库中创建一个特征请求,解释你的用例。Mypy 开发人员可能会考虑在未来的版本中添加对操纵TypedDict
可选性的更直接的支持。 -
替代类型检查器: 探索其他类型检查器,例如 Pyright,看看它们是否提供了处理这种情况的机制。
关于你方法的说明:
-
你关于
NotRequired
是typing._SpecialForm
的理解是正确的,这表明它具有特殊的类型检查语义。 -
Mypy 插件体系结构主要用于分析和修改类型,但它没有提供对
TypedDict
定义的底层结构的细粒度控制。
我理解这些限制可能会令人沮丧,但我希望这些替代方案能为你提供一些前进的途径!如果你还有其他问题或需要进一步的帮助,请告诉我。
标签:python,python-typing,mypy From: 77968372