键入提示条件可变参数应用程序

问题描述 投票:5回答:1

我试图键入提示构造函数的部分应用程序,一旦提供“tag”实例,它就会被完全应用。这是通过包装类实现的,包装类存储构造函数和任何部分应用的参数。由于包装器适用于多种类型,因此必须采用可变的*args

这会产生两种情况,它们所采用的参数不同:

  1. 适用:(tag: Tag, ...) -> Cls
  2. 商店:(...) -> Partial[Cls]

值得注意的是,2.情况可能会或可能不会接收第一个参数。它们都是重叠的,因为它们是可变的。这很容易实现。我试图使用@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]扩展第二个重载并不能解决问题。两种工具都报告类型不匹配,不兼容的重载,或者回退到AnyUnion

任何有关正确提示这种类型的帮助都表示赞赏。


键入检查代码示例:

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正确识别abc的类型,但由于不兼容的重载重叠而拒绝该程序:

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]]
python pycharm type-hinting mypy
1个回答
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:])
© www.soinside.com 2019 - 2024. All rights reserved.