简体   繁体   English

如何对返回类型取决于参数的输入类型的函数进行类型提示?

[英]How can I type-hint a function where the return type depends on the input type of an argument?

Assume that I have a function which converts Python data-types to Postgres data-types like this:假设我有一个函数可以将 Python 数据类型转换为 Postgres 数据类型,如下所示:

def map_type(input):
    if isinstance(input, int):
        return MyEnum(input)
    elif isinstance(input, str):
        return MyCustomClass(str)

I could type-hint this as:我可以输入提示为:

def map_type(input: Union[int, str]) -> Union[MyEnum, MyCustomClass]: ...

But then code like the following would fail to type-check even though it is correct:但是,即使它是正确的,下面的代码也会无法进行类型检查:

myvar = map_type('foobar')
print(myvar.property_of_my_custom_class)

Complete example (working code, but errors in type-hinting):完整示例(工作代码,但类型提示错误):

from typing import Union
from enum import Enum


class MyEnum(Enum):
    VALUE_1 = 1
    VALUE_2 = 2


class MyCustomClass:

    def __init__(self, value: str) -> None:
        self.value = value

    @property
    def myproperty(self) -> str:
        return 2 * self.value


def map_type(value: Union[int, str]) -> Union[MyEnum, MyCustomClass]:

    if isinstance(value, int):
        return MyEnum(value)
    elif isinstance(value, str):
        return MyCustomClass(value)
    raise TypeError('Invalid input type')


myvar1 = map_type(1)
print(myvar1.value, myvar1.name)

myvar2 = map_type('foobar')
print(myvar2.myproperty)

I'm aware that I could split up the mapping into two functions, but the aim is to have a generic type-mapping function.我知道我可以将映射拆分为两个函数,但目的是拥有一个通用类型映射函数。

I was also thinking about working with classes and polymorphism, but then how would I type-hint the topmost class methods?我也在考虑使用类和多态性,但是我将如何对最顶层的类方法进行类型提示呢? Because their output type would depend on the concrete instance type.因为它们的输出类型将取决于具体的实例类型。

This is exactly what function overloads are for. 这正是函数重载的用途。

In short, you do the following: 简而言之,您执行以下操作:

from typing import overload

# ...snip...

@overload
def map_type(value: int) -> MyEnum: ...

@overload
def map_type(value: str) -> MyCustomClass: ...

def map_type(value: Union[int, str]) -> Union[MyEnum, MyCustomClass]:
    if isinstance(value, int):
        return MyEnum(value)
    elif isinstance(value, str):
        return MyCustomClass(value)
    raise TypeError('Invalid input type')

Now, when you do map_type(3) , mypy will understand that the return type is MyEnum . 现在,当你执行map_type(3) ,mypy会理解返回类型是MyEnum

And at runtime, the only function to actually run is the final one -- the first two are completely overridden and ignored. 在运行时,实际运行的唯一功能是最后一个 - 前两个被完全覆盖并被忽略。

If your return type is the same as your input type (or, as my example shows, a variation ) you can use the follow strategy (and remove the need to add additional @overload s when more types are introduced/supported).如果您的返回类型与您的输入类型相同(或者,如我的示例所示,一个变体),您可以使用以下策略(并且在引入/支持更多类型时无需添加额外的@overload )。

from typing import TypeVar

T = TypeVar("T")  # the variable name must coincide with the string

def filter_category(category: T) -> list[T]:
  # assume get_options() is some function that gets
  # an arbitrary number of objects of varying types
  return [
    option
    for option in get_options()
    if is subclass(option, category)
  ]

then using filter_category would correctly be associated by both your IDE (VSCode, Pycharm, etc) and by your static type checker (Mypy)然后使用filter_category将由您的 IDE(VSCode、Pycharm 等)和您的静态类型检查器(Mypy)正确关联

# for instance list_of_ints would show in your IDE as of type list[Type[int]]
list_of_ints = filter_category(int)

# or here it list_of_bools would show in your IDE as of type list[Type[bool]]
list_of_bools = filter_category(bool)

This type definition is much more specific than using this这个类型定义比使用这个更具体

def overly_general_filter(category: Any) -> list[Any]:
  pass

it really would be equivalent to它真的相当于

def equally_general_filter(category) -> list:
  pass

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM