键入一个函数参数,该函数参数派生自Python中的多个抽象基类

时间:2016-05-19 16:09:36

标签: python mypy

我正在尝试根据PEP 484在Python 2中键入注释函数。该函数接受一个应该同时实现__len____iter__的容器。我想要添加此注释的原始代码非常复杂,因此请考虑一个示例函数,如果int为偶数并返回1,则返回容器s中所有len(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"

1 个答案:

答案 0 :(得分:1)

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

SizedIterable不起作用的原因是,通过从SizedIterable派生,您只是告诉mypy它是这两种类型的子类型; mypy 得出的结论是,SizedIterable的子类型的任何类型也是SizedIterable的子类型。

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

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需要为其类型系统添加一个新功能。

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

  • intersections(正如@PiotrĆwiek所指出的那样);您要求的是IterableSized
  • 的交集
  • structural typing;这比一般的交叉点简单得多,但对于您的用例来说已经足够了,因为numbers参数只需要__len____iter__方法

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