我目前正在尝试创建一个类型化的请求包装器,并允许您提供响应处理程序。此响应处理程序可能会更改 http 请求的返回值。
不幸的是,我无法克服一些打字问题(我对使用 python 打字还很陌生)。这对我来说似乎是一个普遍问题或误解,这就是我创建这篇文章的原因。我已经阅读了几个概念,如泛型或协议,但我无法创建一个可行的解决方案,我也不认为我完全理解哪种解决方案可能是最好的。
这是我的示例代码:
from collections.abc import Callable
from typing import TypeVar
from requests import Response
_T = TypeVar("_T", default=Response)
def identity(response: Response) -> Response:
return response
def extract_text(response: Response) -> str:
return response.text
class HTTPRequestsAPI:
def fake_request(self, response_handler: Callable[[Response], _T] = identity) -> _T:
response = Response()
processed_response = response_handler(response)
return processed_response
if __name__ == "__main__":
request_api = HTTPRequestsAPI()
resp = request_api.fake_request()
print(type(resp))
resp_text = request_api.fake_request(response_handler=extract_text)
print(type(resp_text))
我可以毫无问题地运行主程序并获得以下输出(类型),这符合预期:
<class 'requests.models.Response'>
<class 'str'>
当我尝试运行 mypy 时,出现错误,我不明白为什么会发生这种情况,因为我认为应该正确推断类型:
error: Incompatible default for argument "response_handler" (default has type "Callable[[Response], Response]", argument has type "Callable[[Response], _T]") [assignment]
我没有得到什么,这可能是与 mypy 相关的问题吗?我还没有尝试过像“pyright”这样的另一种类型检查器。非常感谢您的帮助!
**我在此示例中使用 python = 3.13 和 mypy = 1.13.0。我也尝试过不使用 TypeVar 的“默认值”,但这没有什么区别,我认为默认值可能会对打字机有所帮助。
正如
评论中的
@Gaberocksall
所述,上述问题是mypy特有的(参见https://github.com/python/mypy/issues/3737)。一个选择是坚持使用 mypy 并使用 typing.overload
。
我创建了一个实现相同行为但被 mypy 接受的示例:
from collections.abc import Callable
from typing import TypeVar, overload
from requests import Response
_T = TypeVar("_T", default=Response)
def identity(response: Response) -> Response:
return response
def extract_text(response: Response) -> str:
return response.text
class HTTPRequestsAPI:
@overload
def fake_request(self, response_handler: Callable[[Response], _T]) -> _T:
...
@overload
def fake_request(self) -> Response:
...
def fake_request(self, response_handler=identity):
response = Response()
processed_response = response_handler(response)
return processed_response
if __name__ == "__main__":
request_api = HTTPRequestsAPI()
resp = request_api.fake_request()
print(type(resp))
resp_text = request_api.fake_request(response_handler=extract_text)
print(type(resp_text))
另一方面,
@InSync
在评论中提到,一个可能的解决方案是切换到pyright,因为pyright将解析发布的代码而没有任何错误(我可以验证这一点)。
总之,这可能是个人偏好,但我会改用pyright以避免“不必要的”超载。在我的“真实”项目中,这会破坏代码库,并使其在我看来变得非常不可读。
非常感谢评论中提供的帮助!