7

我正在尝试根据PEP 484在 Python 2 中键入注释函数。该函数接受一个应该同时实现__len__和的容器__iter__。我要添加此注释的原始代码非常复杂,因此考虑一个示例函数,如果是偶数则返回int容器中所有 s的乘积,否则返回 1。slen(s)

如果我想在__len__需要的地方注释容器,我会将它注释为type: (Sized) -> int. 如果我想在__iter__需要的地方注释容器,我会将它注释为type: (Iterable[int]) -> int. 但是我如何完美地注释一个我需要两者的容器呢?

我按照Piotr-Ćwiek的建议尝试了这个:

from __future__ import print_function
from typing import Sized, Iterable

class SizedIterable(Sized, Iterable[int]):
    pass

def product2(numbers):
    # type: (SizedIterable) -> int
    if len(numbers)%2 == 1:
        return 1
    else:
        p = 1
        for n in numbers:
            p*= n
        return p

print(product2([1, 2, 3, 4]))
print(product2({1, 2, 3, 4}))

但这失败了这个错误:

prod2.py:17: error: Argument 1 to "product2" has incompatible type List[int]; expected "SizedIterable"
prod2.py:18: error: Argument 1 to "product2" has incompatible type Set[int]; expected "SizedIterable"
4

1 回答 1

2

在 python 3.6 中,typing.Collection它几乎完美地适用于您的用例(它也派生自Container,但实际上您想要使用的任何东西都可能具有__contains__)。不幸的是,python 2 没有解决方案。

SizedIterable不起作用的原因是,通过从Sizedand派生它Iterable,您只是告诉 mypy 它是这两种类型的子类型;mypy 不会断定任何类型是 的子类型Sized并且Iterable也是SizedIterable.

Mypy 是完全合乎逻辑的。毕竟,您不希望此代码键入检查:

class A(Sized, Iterable[int]):
  def g(self) -> None:
    ...

def f(x: A) -> None:
  a.g()

# passes type check because [1, 2] is Sized and Iterable
# but fails in run-time
f([1, 2])

如果 mypy 仅仅因为它的类体是空的而对你的类定义进行不同的处理,那就太棘手了。

为了让 mypy 理解您的意图,mypy 需要在其类型系统中添加一个新功能。

目前正在考虑这种功能的两种选择:

  • 十字路口(正如@PiotrĆwiek 指出的那样);你要的是Iterable和的交集Sized
  • 结构类型;这比一般的交叉点要简单得多,但对于您的用例来说已经足够了,因为您只需要拥有numbers参数__len____iter__方法

模仿typing类定义或使用__instancecheck__是行不通的,因为(正如最近有人向我解释的那样)在任何情况下都mypy不会运行您编写的代码(即,它永远不会导入您的模块,永远不会调用您的函数等)。这是因为 mypy 是一个静态分析工具,它不假定运行代码的环境在其执行期间甚至是可用的(例如,python 版本、库等)。

于 2017-04-01T22:10:45.827 回答