如何修复 mypy 以允许从带有接口的字典推断类型

问题描述 投票:0回答:1

所以我们有一个非常简单的注射器,正好适合我们的需要。现在我们想要类型推断,但我们很难让它与 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 不这么认为。那么知道如何使这种注射类型安全吗?

python python-3.x mypy python-typing
1个回答
2
投票

让我们一一检查你的错误。


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 高度 有限的输入能力,我们对此无能为力。

如果您提供更多上下文并解释您要在这里完成什么,我可以尝试修改此答案以为您提供更具体的解决方案。 (您可以在这里编辑您的问题和评论。)

© www.soinside.com 2019 - 2024. All rights reserved.