【发布时间】:2022-01-21 22:20:52
【问题描述】:
我有一个非常简单的类,它继承自 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:错误:“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 解决方法。
所以:
-
我认为我的函数签名尽可能正确 它,但是
url上的类型注释是否正确或者它们是 不正确并导致问题? -
urljoin周围的错误是怎么回事?
来自 cmets,这是:
if self.baseurl and not url.startswith(
"http" if isinstance(url, str) else b"http"
):
失败:
example.py:25:错误:“str”的“startswith”的参数 1 具有不兼容的类型“Union[str, bytes]”;预期“Union[str, Tuple[str, ...]]”
example.py:25:错误:“bytes”的“startswith”的参数 1 具有不兼容的类型“Union[str, bytes]”;预期“联合[字节,元组[字节,...]]”
【问题讨论】:
-
问题是如果
_url是一个bytes值,那么_url.startswith("http")是一个实际的runtime错误;它必须是_url.startswith(b"http")。演员表可能会让mypy开心,但如果_url真的是bytes值,它会在运行时失败。 -
我知道我从来没有用
bytes值调用request,所以我不太担心这种可能性。我想正确的处理方法是扔进isinstance(bytes, url)并酌情解码? -
您可以将参数声明为
str。子类方法不必接受与父类相同的所有类型。 -
您甚至不必解码它,因为
request已经可以处理这两个问题。只需_url.startswith("http" if isinstance(str, url) else b"http")就足够了。 -
@Grismar 你不能那样做,因为那样你就会以一种与
Session不兼容的方式限制你接受的类型。如果您在运行时确实收到了bytes值,则引发异常 很好,但从静态类型的角度来看,您仍然必须首先接受它。