[英]Fighting python type annotations
我有一个非常简单的 class 继承自requests.Session
。 代码目前看起来像:
import requests
import urllib.parse
from typing import Any, Optional, Union, cast
default_gutendex_baseurl = "https://gutendex.com/"
class Gutendex(requests.Session):
def __init__(self, baseurl: Optional[str] = None):
super().__init__()
self.baseurl = baseurl or default_gutendex_baseurl
def search(self, keywords: str) -> Any:
res = self.get("/books", params={"search": keywords})
res.raise_for_status()
return res.json()
def request(
self, method: str, url: Union[str, bytes], *args, **kwargs
) -> requests.Response:
if self.baseurl and not url.startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
return super().request(method, url, *args, **kwargs)
我很难让mypy
对request
方法感到满意。
第一个挑战是获取要验证的参数; 设置url: Union[str, bytes]
是匹配types-requests
中的类型注释所必需的。 我刚刚举手使*args
和**kwargs
正确,因为唯一的解决方案似乎是重现单个参数注释,但我很高兴将其保留。
处理完 function 签名后, mypy
现在抱怨对startswith
的调用:
example.py:23:错误:“bytes”的“startswith”的参数 1 具有不兼容的类型“str”; 预期“联合[字节,元组[字节,...]]”
我可以通过明确的cast
来解决这个问题:
if not cast(str, url).startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
...但这似乎只是引入了复杂性。
然后对urllib.parse.urljoin
的调用不满意:
example.py:24:错误:“urljoin”的类型变量“AnyStr”的值不能是“Sequence[object]”
example.py:24:错误:赋值类型不兼容(表达式的类型为“Sequence[object]”,变量的类型为“Union[str, bytes]”)
我不确定如何处理这些错误。
我现在通过将显式强制转换移动到方法的顶部来解决问题:
def request(
self, method: str, url: Union[str, bytes], *args, **kwargs
) -> requests.Response:
_url = url.decode() if isinstance(url, bytes) else url
if not _url.startswith("http"):
_url = urllib.parse.urljoin(self.baseurl, _url)
return super().request(method, _url, *args, **kwargs)
但这感觉像是一个 hacky 解决方法。
所以:
我想我的 function 签名和我想得到的一样正确,但是url
上的类型注释是正确的还是不正确并导致问题?
urljoin
周围的错误是怎么回事?
从评论中可以看出:
if self.baseurl and not url.startswith(
"http" if isinstance(url, str) else b"http"
):
失败:
example.py:25:错误:“str”的“startswith”的参数 1 具有不兼容的类型“Union[str, bytes]”; 预期“联合[str,元组[str,...]]”
example.py:25:错误:“bytes”的“startswith”的参数 1 具有不兼容的类型“Union [str, bytes]”; 预期“联合[字节,元组[字节,...]]”
这解决了整个问题:
import requests
import urllib.parse
from typing import Union, cast
default_gutendex_baseurl = "https://gutendex.com/"
class Gutendex(requests.Session):
def __init__(self, baseurl: str = None):
super().__init__()
self.baseurl = baseurl or default_gutendex_baseurl
def search(self, keywords: str) -> dict[str, str]:
res = self.get("/books", params={"search": keywords})
res.raise_for_status()
return res.json()
def request(
self, method: str, url: Union[str, bytes], *args, **kwargs
) -> requests.Response:
if isinstance(url, str):
if not url.startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
return super().request(method, url, *args, **kwargs)
else:
raise TypeError('Gutendex does not support bytes type url arguments')
如果你说你接受它,你不能只是不处理bytes
。 如果bytes
被传递,只需引发异常或做一些更好的事情。 或者如果你喜欢危险地生活,甚至只是pass
。
此代码在mypy
中验证得很好。
有点令人失望的是,这样的事情无法验证:
if not url.startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url if isinstance(url, str) else url.decode())
return super().request(method, url, *args, **kwargs)
即使url.startswith
无法在str
时获取bytes
,反之亦然,它仍然无法验证。 mypy
无法通过运行时逻辑进行验证,因此您只能执行以下操作:
def request(
self, method: str, url: Union[str, bytes], *args, **kwargs
) -> requests.Response:
if isinstance(url, str):
if not url.startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
return super().request(method, url, *args, **kwargs)
else:
if not url.startswith(b"http"):
url = urllib.parse.urljoin(self.baseurl, url.decode())
return super().request(method, url, *args, **kwargs)
它支持两者,但以丑陋的方式重复逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.