【问题标题】:Type hint conditional variadic application类型提示条件变量应用
【发布时间】:2019-02-27 13:43:36
【问题描述】:

我正在尝试对构造函数的部分应用进行类型提示,一旦提供了“标记”实例,该构造函数就会完全应用。这是通过一个包装类实现的,该类存储构造函数和任何部分应用的参数。由于包装器适用于多种类型,因此必须采用可变参数*args

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

  1. 申请:(tag: Tag, ...) -> Cls
  2. 商店:(...) -> 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] 扩展第二个重载并不能解决问题。这两个工具要么报告类型不匹配、不兼容的过载,要么回退到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]]

【问题讨论】:

  • Partial 做了哪些 functools.partial 不做的事情?
  • @chepner Partial 在非密封应用上自我应用,允许进一步的部分应用(它有效地咖喱 ctor)。密封更类似于partialmethod,但在应用程序上显式绑定而不是__get__。我们使用它来增量准备实例原型,然后将它们绑定到 DAG 中。

标签: python pycharm type-hinting mypy


【解决方案1】:

我不是打字专家,但我认为您无法仅通过更改类型提示来实现此功能。 Mypy 无法知道在包含 Tag 时调用了哪些重载方法,因为它可能属于任一用例。

例如,就目前而言,您的代码在任何一种情况下都允许将 Tag 变量传递给 *args 变量,因此类型提示中没有任何内容可以告诉用户您不能尝试构造存储多个标签变量的部分实例。

我不知道这是否适用于您的解决方案,但由于您已经在手动检查第一个 args 变量的类型,您可以坚持使用标记或显式无,将第二个重载更改为:

@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:])

【讨论】:

  • mypy 确实知道哪种情况适用,并正确推断出返回类型。它选择第一个匹配的案例,这也是最具体的。 mypy 的问题在于它坚持返回类型是兼容的。我不介意将*argsargs[0] 指定为“not Tag”,但不知道如何。
  • 抱歉,我的意思是我相信 mypy 会抛出关于不兼容类型的错误,因为它无法知道用户永远不会期望在提供 Tag 类型的 args 时调用第二种情况。我考虑将变量声明为不是类型,但在文档中找不到任何内容。
猜你喜欢
  • 2013-09-28
  • 2010-12-21
  • 2017-05-20
  • 2010-12-20
  • 2013-12-26
  • 1970-01-01
  • 1970-01-01
  • 2015-11-07
  • 2018-05-18
相关资源
最近更新 更多