我正在尝试提示构造函数的部分应用程序,一旦提供了“标签”实例,该应用程序将被完全应用。这是通过包装类实现的,该包装类存储构造函数和任何部分应用的参数。由于包装程序适用于多种类型,因此必须采用可变参数*args
。
这将创建两种情况,这两种情况因采用的参数而异:
(tag: Tag, ...) -> Cls
(...) -> Partial[Cls]
值得注意的是,2. case可能会或可能不会接收到第一个参数。由于它们是可变的,所以两者的友善重叠。这很容易实现。我尝试使用@overload
键入这些提示:
from typing import TypeVar, Generic
#: the class to partially construct
Cls = TypeVar('Cls')
class Tag:
"""Instances of this class complete the partial application"""
class Partial(Generic[Cls]):
"""Partially construct ``ctor`` until a :py:class:`~.Tag` is applied"""
def __init__(self, ctor: Type[Cls], *args):
self.ctor = ctor
self.args = args
# type hints
@overload
def __call__(self, tag: Tag, *args) -> Cls:
...
@overload
def __call__(self, *args) -> 'Partial[Cls]':
...
# implementation
def __call__(self, *args):
if args and isinstance(args[0], Tag):
return self.ctor(args[0], *self.args, *args[1:])
return Partial(self.ctor, *self.args, *args)
但是,mypy和PyCharm都不对此感到满意(PyCharm目前需要显式的方法调用,但这不是我的问题)。使用显式非标记(tag: Any, ...) -> Partial[Cls]
扩展第二个重载无法解决该问题。两种工具要么报告类型不匹配,不兼容的重载,要么回退到Any
或Union
。
感谢您提供任何有关正确提示类型的帮助。
类型检查码示例:
class VariadicString(str):
def __new__(cls, *args):
return str(args)
a = RecursivePartial(VariadicString, 1, 2, 3)
b = a(4, 5, 6)
c = b(Tag(), 7, 8, 9)
reveal_locals() # absent for PyCharm
mypy
正确地标识了a
,b
和c
的类型,但是由于不兼容的重载重叠而拒绝了该程序:
test.py:17: error: Overloaded function signatures 1 and 2 overlap with incompatible return types
test.py:38: error: Revealed local types are:
test.py:38: error: a: test.Partial[test.VariadicString*]
test.py:38: error: b: test,Partial[test.VariadicString*]
test.py:38: error: c: test.VariadicString*
PyCharm不会拒绝该程序,但会将c
误识别为两种返回类型的Union
:
a: Partial[VariadicString]
b: Partial[VariadicString]
c: Union[VariadicString, Partial[VariadicString]]
答案 0 :(得分:1)
我不是打字专家,但我认为仅更改类型提示就无法使它起作用。当包含一个Tag时,Mypy无法知道正在调用哪个重载方法,因为它可能会落入任何一种用例中。
例如,就您的代码而言,无论哪种情况,都允许将Tag变量传递给*args
变量,因此类型提示中没有任何内容告诉用户您无法尝试构造一个局部实例,该实例存储几个Tag变量。
我不知道这是否可以解决您的问题,但是由于您已经在手动检查第一个args
变量的类型,因此您可以坚持使用Tag来调用该方法或明确的None,将第二个重载更改为:
@overload
def __call__(self, tag: None, *args) -> 'Partial[Cls]':
...
及其实现:
# implementation
def __call__(self, *args):
if args and isinstance(args[0], Tag):
return self.ctor(args[0], *self.args, *args[1:])
# Don't add the None to the args.
return Partial(self.ctor, *self.args, *args[1:])