如何为类型参数设置 python 类型

How to setup python typing for a type argument

我想为作为参数传递的 Python 类型正确添加类型。例如。让我们假设我们想为以下函数添加类型:

def do_something_based_on_types(
    ...
    type_name: str,
    str_to_type_mapping: Dict[str, Any], # what instead of Any?
):
    ...
    object = str_to_type_mapping[type_name]()
    ...

我们希望将映射从 str 传递到类型,我们希望根据该类型构建所选 class 的对象。此场景的正确类型是什么(而不是代码示例中使用的 Any)。

你的例子很有活力。由于 Python 类型注释的主要目的是支持 static 分析,因此它们在这里可以为我们提供的内容是有限的。但是,我确实认为我们可以做得比 Any!

(注意:为了防止混淆,我会将您代码段中名为 object 的变量称为 my_object,当我使用 object 时,我的意思是 Python类型)。

使用typing.Type

由于您提到提供的字典中的值应该是可以启动的类型,因此合理的类型注释应该是 Dict[str, Type[object]]Dict[str, Type],后者等同于 Dict[str, Type[Any]]。第一个是最严格的,它将限制类型检查器允许您对 my_object 变量执行的操作。第二个只比普通 Any 稍微好一点,尽管它确实会在调用者不小心提供实例而不是类型时警告调用者。

typing.Callable?

根据您提供的代码段,使用字典值的唯一方法是创建一个新实例。类型并不是唯一可以做到这一点的东西。实际上,任何采用零参数和 returns 对象的可调用对象(一个普通函数,一个 class 方法)也足够了。因此,使用 Callable[[], object] 将为函数的调用者提供更大的灵活性,并且还会在传递没有零参数构造函数的类型时发出警告。我更喜欢这个而不是 Type,因为它看起来更安全,而且更符合 Python 鸭子打字的精神。

有比普通对象更好的东西吗?

这两种解决方案都有一个局限性,即在您的函数内部,我们对 my_object 的类型一无所知。在一般情况下,每次我们执行对象类型不支持的操作时,我们都会 'forced' 进行 isinstance 检查。但是,也许在您的用例中,单个字典不应该只包含任何类型。可能是:

  1. 所有类型都有一个共同的基数 class,比如 MyBase
  2. 所有类型都应该支持一组通用操作(方法或属性)。

在情况 (1) 中,我们可以将 object 类型替换为 MyBase 类型。类型检查器应该能够确保 MyBase 支持的操作可以安全使用,并在提供的字典包含其他类型时发出警告。

对于情况(2),typing.Protocol可以帮到我们。您可以使用所需的操作定义自定义协议,并将其添加到类型注释中:

class MyCustomProto(Protocol):
    foo: int
    def bar(self) -> str: ...

现在类型检查器应该知道 my_object 应该具有整数属性 foo 和一个方法 bar 即 returns 一个字符串。请注意,Protocol 已添加到 Python 3.8,并且可通过 typing-extensions 包用于以前的版本。

Dict 与 typing.Mapping

与你的问题没有直接关系,但如果你只是用字典来阅读,你应该考虑将Dict替换为Mapping类型。这允许调用者提供除 dict subclasses 之外的任何映射类型(在实践中不太可能发生)。但更重要的是,它会在不小心修改提供的映射时发出警告,这会导致很难发现错误。因此,我的最终建议是 Mapping[str, Callable[[], <X>],其中 <X>objectMyCustomProtoMyBase.

之一