战斗python类型注解
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
正确,因为唯一的解决方案似乎是
重现各个参数注释,但我很高兴
保持原样。
随着函数签名的处理,mypy
现在正在抱怨
关于对 startswith
的调用:
example.py:23: error: Argument 1 to "startswith" of "bytes" has incompatible type "str"; expected "Union[bytes, Tuple[bytes, ...]]"
我可以用明确的方式解决这个问题 cast
:
if not cast(str, url).startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
...但这似乎只是引入了复杂性。
然后对urllib.parse.urljoin
的调用不满意:
example.py:24: error: Value of type variable "AnyStr" of "urljoin" cannot be "Sequence[object]"
example.py:24: error: Incompatible types in assignment (expression has type "Sequence[object]", variable has type "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)
但这感觉像是一个棘手的解决方法。
所以:
我想我的函数签名和我想要的一样正确
它,但是 url
上的类型注释是正确的还是它们
不正确并导致问题?
urljoin
周围的错误是怎么回事?
根据评论,这个:
if self.baseurl and not url.startswith(
"http" if isinstance(url, str) else b"http"
):
失败:
example.py:25: error: Argument 1 to "startswith" of "str" has incompatible type "Union[str, bytes]"; expected "Union[str, Tuple[str, ...]]"
example.py:25: error: Argument 1 to "startswith" of "bytes" has incompatible type "Union[str, bytes]"; expected "Union[bytes, Tuple[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)
两者都支持,但以丑陋的方式重复了逻辑。
我有一个非常简单的 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
正确,因为唯一的解决方案似乎是
重现各个参数注释,但我很高兴
保持原样。
随着函数签名的处理,mypy
现在正在抱怨
关于对 startswith
的调用:
example.py:23: error: Argument 1 to "startswith" of "bytes" has incompatible type "str"; expected "Union[bytes, Tuple[bytes, ...]]"
我可以用明确的方式解决这个问题 cast
:
if not cast(str, url).startswith("http"):
url = urllib.parse.urljoin(self.baseurl, url)
...但这似乎只是引入了复杂性。
然后对urllib.parse.urljoin
的调用不满意:
example.py:24: error: Value of type variable "AnyStr" of "urljoin" cannot be "Sequence[object]"
example.py:24: error: Incompatible types in assignment (expression has type "Sequence[object]", variable has type "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)
但这感觉像是一个棘手的解决方法。
所以:
我想我的函数签名和我想要的一样正确 它,但是
url
上的类型注释是正确的还是它们 不正确并导致问题?urljoin
周围的错误是怎么回事?
根据评论,这个:
if self.baseurl and not url.startswith(
"http" if isinstance(url, str) else b"http"
):
失败:
example.py:25: error: Argument 1 to "startswith" of "str" has incompatible type "Union[str, bytes]"; expected "Union[str, Tuple[str, ...]]"
example.py:25: error: Argument 1 to "startswith" of "bytes" has incompatible type "Union[str, bytes]"; expected "Union[bytes, Tuple[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)
两者都支持,但以丑陋的方式重复了逻辑。