所以我们有一个非常简单的注射器,正好适合我们的需要。现在我们想要类型推断,但我们很难让它与 mypy 一起工作。这里有一个重现问题的缩小示例:
from abc import ABC, abstractmethod
from typing import Type, TypeVar, Dict
class Interface(ABC):
@abstractmethod
def method(self):
pass
class Implementation(Interface):
pass
T = TypeVar("T", covariant=True)
def get_implementation(interface: Type[T], *args, **kwargs) -> T:
dictionary: Dict[Type[T], T] = {Interface: Implementation}
return dictionary[interface](*args, **kwargs)
implementation = get_implementation(Interface)
您可以在 https://mypy-play.net/?mypy=0.971&python=3.6 上运行该代码。您将收到以下错误:
main.py:15: error: Dict entry 0 has incompatible type "Type[Interface]": "Type[Implementation]"; expected "Type[T]": "T"
main.py:16: error: "object" not callable
main.py:19: error: Only concrete class can be given where "Type[Interface]" is expected
老实说,我不知道如何修复它们。对我来说,代码似乎是正确的,但显然 mypy 不这么认为。那么知道如何使这种注射类型安全吗?
让我们一一检查你的错误。
Dict entry 0 has incompatible type "Type[Interface]": "Type[Implementation]"; expected "Type[T]": "T"
您根据
get_implementation
类型将 T
函数定义为通用函数,它将接收作为其 interface
参数的类型。这意味着在函数的body内部,类型T
是bound到作为interface
参数传递的任何内容。
然后,您在
T
变量的注释中使用该类型 dictionary
,指定其键类型应为 Type[T]
,但是随后您为该变量分配一个键类型为 Type[Interface]
: 的字典
def get_implementation(interface: Type[T], *args, **kwargs) -> T:
dictionary: Dict[Type[T], T] = {Interface: Implementation}
这是错误的,因为在调用该函数时,
T
可能绑定到 Interface
以外的其他东西。例如,它可以用 Foo
的一些子类 Interface
来调用。在这种情况下,T
将解析为函数体中的子类 Foo
,而 dictionary
变量的键类型将是 Type[Foo]
。这意味着分配键类型为Type[Interface]
的字典是错误的。
事实上,由于您甚至没有在
Interface
上设置上限(比如 T
),您可以自由调用 get_implementation
并使用 几乎任何类型 作为第一个参数。例如,通过使用 int
调用它,T
将绑定到 int
,因此只允许 dictionary
键为 int
.
对于该字典的值类型,这是相同的处理。但这里还有一个问题。您分配给该变量的字典具有
Type[Implementation]
类型的值,但您的注释表示值类型应该只是 T
。本质上,您的注释 Dict[Type[T], T]
表示字典值必须是用作其键的类的instances。但是Implementation
不是Interface
的实例,它是它的子类型/子类。
"object" not callable
在这里,我们正在处理
T
上缺少上限以及该 dictionary
变量(特别是它的值类型)的奇怪注释的结果。
由于
T
可以是字面意义上的任何类型(无限制)并且 interface
参数是 Type[T]
类型,类型检查器可以对其做出的最具体的推断是它将是 some type
子类型.换句话说,最通用的类型。
最一般类型的实例就是
object
。由于您的 dictionary
被注释为包含该类型的值(即 object
),因此执行 dictionary[some_key](...)
是不安全的。假设该字典的值是 callable 是不安全的,因为对象通常是不可调用的。
Only concrete class can be given where "Type[Interface]" is expected
这个相当简单。传递抽象基类通常是不安全的,因为它们明确地不打算(或根本不能)被实例化。您正在调用
get_implementation
并将 ABC Interface
传递给它。类型通常意味着被实例化(至少当作为普通函数参数提供时)。
我只能假设您丢弃了很多上下文以使示例最小化,但是代码引发了 lot 关于您在这里实际尝试完成什么的问题。您多次使用术语“注入”,但该术语含糊不清(坦率地说,在这种情况下毫无意义)。
它看起来有点像你只是有一个替代构造函数或某种工厂用于
Interface
子类,尽管那个字典高度可疑。
如果你想要一个工厂,一个工作和(某种程度上)类型安全的例子可能看起来像这样:
from abc import ABC, abstractmethod
from typing import Any, Type, TypeVar
class Interface(ABC):
@abstractmethod
def method(self) -> Any:
pass
T = TypeVar("T", bound=Interface)
def get_implementation(interface: Type[T], *args: Any, **kwargs: Any) -> T:
...
return interface(*args, **kwargs)
class Implementation(Interface):
def method(self) -> None:
print("hi mom")
implementation = get_implementation(Implementation)
reveal_type(implementation) # Revealed type is "Implementation"
构造函数参数当然不是真正安全的,但是由于 Python 3.6 高度 有限的输入能力,我们对此无能为力。
如果您提供更多上下文并解释您要在这里完成什么,我可以尝试修改此答案以为您提供更具体的解决方案。 (您可以在这里编辑您的问题和评论。)