Python类型提示:Callable后跟着TypeVar是什么意思?

4

我正在尝试理解以下代码中的类型提示Getter[T]:

简化示例

T = TypeVar('T')
Getter = Callable[[T, str], str]


class AbstractClass(abc.ABC):
    @abc.abstractmethod
    def extract(
        self,
        get_from_carrier: Getter[T],  #  <---- See here
        ...
    ) -> Context:

非常感谢帮助,因为我已经在这上面费了很大的脑力。

原始源代码

原始源代码来自于OpenTelemetry项目文件"textmap.py"

import abc
import typing

from opentelemetry.context.context import Context

TextMapPropagatorT = typing.TypeVar("TextMapPropagatorT")

Setter = typing.Callable[[TextMapPropagatorT, str, str], None]
Getter = typing.Callable[[TextMapPropagatorT, str], typing.List[str]]


class TextMapPropagator(abc.ABC):
    """This class provides an interface that enables extracting and injecting
    context into headers of HTTP requests. 
    ...
    """

    @abc.abstractmethod
    def extract(
        self,
        get_from_carrier: Getter[TextMapPropagatorT],
        carrier: TextMapPropagatorT,
        context: typing.Optional[Context] = None,
    ) -> Context:

至少意图是,您传递给 get_from_elem 的第二个参数具有 get 函数期望的第一个参数类型。 - chepner
不一定;get_from_elem(get: C_, elem: str)并不意味着getelem之间有任何联系。 - chepner
不,我的意思是 get_from_elem(get: _C, elem: _T)_C 的额外下标似乎试图复制已经在 _C 中的信息。 - Carcigenicate
如果有帮助的话,我在发布问题时简化了变量名,以便理解这段代码:https://github.com/open-telemetry/opentelemetry-python/blob/master/opentelemetry-api/src/opentelemetry/trace/propagation/textmap.py#L38 - RASHMI K A
@Carcigenicate 哦,这是可能的。我删除了我的答案并将其变成了评论,因为我在实际使用这种泛型时太生疏了。我不知道 mypy 是否会对这两个进行区分,或者其中任何一个是否符合我的预期意图。 - chepner
显示剩余3条评论
2个回答

2

一个带有类型变量的可调用对象表示该对象是一个泛型函数,可以接受一个或多个泛型类型为 T 的参数。

类型变量 T 是任何泛型类型的参数。

代码行:

Getter = Callable[[T, str], str]

Getter定义为一个类型别名,其可调用函数的参数是泛型T和字符串,并且返回类型为字符串。

因此,下面这行代码:

get_from_carrier: Getter[T]

定义了一个参数(get_from_carrier),它是一个通用函数。通用函数的第一个参数是通用类型T

具体例子

通过查看下面来自"instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/init.py "propagators.extract,可以更好地理解这一点:

在调用propagators.extract时,函数get_header_from_scope是一个可调用函数,其第一个参数的类型为dict,而这个dict作为TextMapPropagatorT

def get_header_from_scope(scope: dict, header_name: str) -> typing.List[str]:
    """Retrieve a HTTP header value from the ASGI scope.

    Returns:
        A list with a single string with the header value if it exists, else an empty list.
    """
    headers = scope.get("headers")
    return [
        value.decode("utf8")
        for (key, value) in headers
        if key.decode("utf8") == header_name
    ]


...


class OpenTelemetryMiddleware:
    """The ASGI application middleware.
    ...
    """

    ...

    async def __call__(self, scope, receive, send):
        """The ASGI application  ... """
        if scope["type"] not in ("http", "websocket"):
            return await self.app(scope, receive, send)

        token = context.attach(
            propagators.extract(get_header_from_scope, scope)
        )

0

简而言之:_C[_T]是一个通用类型别名,等同于Callable[[_T, int], int]


在这里,您正在定义_CCallable[[_T, int], int]的类型别名。当类型别名包含TypeVar(在本例中为_T)时,它变成了通用类型别名。您可以像使用内置通用类型List[T]Dict[K,V]一样使用它,例如,_C[str]等同于Callable[[str,int],int]

然后,get_from_elem的类型注释将其定义为通用函数。这意味着整个函数中使用的相同类型变量应绑定到同一类。为了解释这意味着什么,请看一下这些函数调用:

_T = typing.TypeVar('_T')
_C = typing.Callable[[_T,int],int]

def get_from_elem(get: _C[_T], elem: _T):
    ...

def foo_str(a: str, b: int) -> int:
    # This function matches `_C[str]`, i.e. `Callable[[str, int], int]`
    ...

def foo_float(a: float, b: int) -> int:
    # This function matches `_C[float]`, i.e. `Callable[[float, int], int]`
    ...

def foo_generic(a: _T, b: int) -> int:
    # This function matches `_C[_T]`, it is also a generic function
    ...

_T2 = typing.TypeVar('_T2', str, bytes)

def foo_str_like(a: _T2, b: int) -> int:
    # A generic function with constraints: type of first argument must be `str` or `bytes`
    ...


get_from_elem(foo_str, "abc")      # Correct: `_T` is bound to `str`
get_from_elem(foo_float, 1.23)     # Correct: `_T` is bound to `float`

get_from_elem(foo_str, 1.23)       # Wrong: `_T` bound to two different types `str` and `float`
get_from_elem(foo_float, [1.23])   # Wrong: `_T` bound to two different types `float` and `List[float]`

get_from_elem(foo_generic, 1.45)   # Correct: `_T` is only bound to `float`
get_from_elem(foo_str_like, 1.45)  # Wrong: `_T` is only bound to `float`, but doesn't satisfy `foo_str_like` constraints

在最后两种情况下,第一个参数是一个通用函数,它不绑定类型变量,因此类型变量只能由第二个参数绑定。然而,在最后一种情况下,foo_str_like 对其第一个参数类型有额外的约束,并且绑定的类型 float 不满足该约束,因此无法通过类型检查。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接