Python 类型提示:Callable 后跟 TypeVar 是啥意思?
Posted
技术标签:
【中文标题】Python 类型提示:Callable 后跟 TypeVar 是啥意思?【英文标题】:Python type hints: What does Callable followed by a TypeVar mean?Python 类型提示:Callable 后跟 TypeVar 是什么意思? 【发布时间】:2021-01-13 06:35:10 【问题描述】:我正在尝试理解以下代码中的类型提示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 project file "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 那已经和C_
单独沟通了,不是吗?
不一定; get_from_elem(get: C_, elem: str)
不会暗示 get
和 elem
之间有任何联系。
不,我的意思是get_from_elem(get: _C, elem: _T)
。 _C
的额外下标似乎试图复制 _C
中已有的信息。
【参考方案1】:
tl;dr: _C[_T]
是一个泛型类型别名,等效于 Callable[[_T, int], int]
。
在这里,您将_C
定义为Callable[[_T, int], int]
的类型别名。当类型别名包含TypeVar
(在本例中为_T
)时,它变为generic type alias。您可以像使用内置泛型类型(如 List[T]
或 Dict[K, V]
)一样使用它,例如,_C[str]
将等同于 Callable[[str, int], int]
。
然后,键入get_from_elem
的注释将其定义为generic function。这意味着在整个函数中使用的相同类型变量应该绑定到同一个类。要解释这意味着什么,请看一下这些函数调用:
_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
不满足该约束,因此它无法通过类型检查。
【讨论】:
【参考方案2】:一个 Callable 后跟一个类型变量意味着该 callable 是一个泛型函数,它接受一个或多个泛型类型 T
的参数。
类型变量T
是任何泛型类型的参数。
行:
Getter = Callable[[T, str], str]
将Getter
定义为可调用函数的类型别名,该函数的参数是泛型类型T
和字符串,返回类型是字符串。
因此,行:
get_from_carrier: Getter[T]
定义一个作为通用函数的参数 (get_from_carrier
)。并且泛型函数的第一个参数是泛型类型T
。
具体例子
看一个具体的例子可以更好地理解这一点。请参阅下面的propagators.extract
"instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/init.py ":
在调用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)
)
【讨论】:
以上是关于Python 类型提示:Callable 后跟 TypeVar 是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章