有没有一种更好的方法可以使用Python的输入模块为复合类型创建类型别名?

时间:2019-03-25 11:21:57

标签: python typing typechecking

我有一个带有一个参数的函数,该函数应使用intNone作为参数。有多种方法可以为这种复合类型创建类型别名:

# test.py
import typing

IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None)
IntOrNone_2 = typing.Union[int, None]


def my_func1(xyz: IntOrNone_1):
    return xyz

def my_func2(xyz: IntOrNone_2):
    return xyz


my_func1(12)
my_func1(None)
my_func1(13.7)
my_func1('str')

my_func2(12)
my_func2(None)
my_func2(13.7)
my_func2('str')

这两种方法都可以完成我期望的操作,但是,对应的mypy错误有点不同,但是基本上具有相同的含义。

  

test.py:14:错误:“ my_func1”的类型变量“ IntOrNone_1”的值   不能是“浮动”

     

test.py:15:错误:“ my_func1”的类型变量“ IntOrNone_1”的值   不能是“ str”

     

test.py:19:错误:“ my_func2”的参数1具有不兼容的类型   “浮动”;预期为“可选[int]”

     

test.py:20:错误:“ my_func2”的参数1具有不兼容的类型   “ str”;预期为“可选[int]”

我倾向于使用第二种方法,因为它还会报告哪个参数导致了错误。

我想这两种方法真的等效吗?还是其中一种是首选?

1 个答案:

答案 0 :(得分:3)

这两种方法远非等效。您应该避免将TypeVars看作只是别名-相反,当您要使函数泛型时,它们是您使用的更特殊的形式。

通过示例最容易解释什么是“泛型函数”。假设您要编写一个函数,该函数接受某个对象(任何对象!)并返回另一个完全相同类型的对象。你会怎么做?

我们可以做的一种方法是尝试使用object

def identity(x: object) -> object:
    return x

这使我们接近,因为我们的identity函数至少可以接受任何字面的内容(因为所有类型都从Python中的object继承)。但是,此解决方案有缺陷:如果我们传入一个int,则会返回object,这不是我们想要的。

相反,我们需要的是类型检查器了解这两种类型之间存在“关系”的方法。这正是TypeVar派上用场的地方:

T = TypeVar('T')

def identity(x: T) -> T:
    return x

在这种情况下,我们的TypeVar'T'充当“占位符”,可以绑定到我们想要的任何类型。因此,如果我们进行identity(3),则T将被绑定到int上,因此类型检查器将理解返回类型也必须为{{1 }}!

如果我们在参数类型提示中多次使用int,则类型检查器将确保每次类型都相同。


那么,下面的表达式在做什么?

T

嗯,事实证明,有时在我们的特殊占位符类型中添加 constraints 很有用。例如,您已约束IntOrNone_1 = typing.TypeVar('IntOrNone_1', int, None) ,使其只能绑定到IntOrNone_1int,而不能绑定其他类型。


最后,要回答最后一个问题:在给出的示例中,您绝对应该使用Union,而不是TypeVars。

对于Union,使用Union还是类型别名确实是个人喜好的问题,但是,如果您不需要这种“占位符”或“通用”行为,则不应使用TypeVars。

如果您想了解有关如何使用TypeVars的更多信息,则mypy文档具有section on generics。它涵盖了我跳过的几件事,包括如何制作通用类(不仅仅是通用函数)。