[英]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.