通用工厂方法的类型提示

时间:2019-08-29 07:35:03

标签: python python-3.x type-hinting

我很好奇,是否有可能通过Python中的类型参数推断出通用类型(用于类型提示)。

例如,考虑一种(相当愚蠢的)工厂方法:

from typing import TypeVar, Type

T = TypeVar('T')

class Test1(object):
    def test1(self):
        pass

class Test2(object):
    def test2(self):
        pass

# Is the type hinting correct here?
def create( t: Type[T] ) -> T:
    if t == Test1:
        return Test1()
    elif t == Test2:
        return Test2()
    else:
        raise ValueError()

instance1 = create( Test1 )
instance2 = create( Test2 )

Visual Studio 2017和intellij Ultimate 2018似乎都没有在这里选择instance1和instance2的正确类型,至少在智能方面没有。 不过,我很好奇我的用法是否错误,或者IDE尚不支持这种用法。

谢谢

1 个答案:

答案 0 :(得分:1)

您的类型签名非常好,您的IDE应该报告instanceinstance2分别为Test1Test2类型。实际上,这是如果您尝试针对mypy运行代码所获得的输出。 (要显示mypy认为您的类型是什么,请在运行mypy之前尝试将reveal_locals()伪函数临时添加到代码底部。)


但是,您的代码的实际 body 实际上在技术上也不合理!您的代码的调用者可以传入一个自定义对象,该对象决定始终认为自己等于所有事物。例如:

from typing import TypeVar, Type

class ForceAlwaysEqual(type):
    def __new__(cls, name, bases, dct):
        return super().__new__(cls, name, bases, dct)

    def __eq__(self, other) -> bool:
        return True

class Hijacked(metaclass=ForceAlwaysEqual): pass

class Test1: pass

class Test2: pass

T = TypeVar('T')

def create(t: Type[T]) -> T:
    if t == Test1:
        return Test1()
    elif t == Test2:
        return Test2()
    else:
        raise ValueError()


mystery = create(Hijacked)
print(type(mystery))

如果您尝试在Python中运行此代码,我们将在最后打印出Test1:对create的调用不会引发ValueError!

这里基本上发生的是,我向Hijacked添加了一个自定义元类,因此执行Hijacked == blah总是会返回True。这会导致create(t == Test1)内部的第一笔检查恰好是真的,因此我们返回Test1,这违反了您函数的签名。

因此,如果您的IDE也没有指示create函数的 body 出现任何问题,那么从技术上讲,这实际上是他们的又一个错误。

(这里最普遍的教训是,在Python中,类型检查器很难从相等性检查中推断出很多东西,因为它们可以随时被重新定义以执行任意数量的胡说八道。)