Py学习  »  Python

对抗python类型注释

larsks • 2 年前 • 507 次点击  

我有一个非常简单的类继承自 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 1.我刚举手示意 *args **kwargs 正确,因为唯一的解决方案似乎是 复制单个参数注释,但我很乐意 就这样吧。

处理函数签名后, 麦皮 她现在在抱怨 关于打电话给 startswith :

实例py:23:错误:“字节”的“startswith”的参数1的类型“str”不兼容;应为“联合[字节,元组[字节,…]”

我可以用一个明确的答案来解决这个问题 cast :

        if not cast(str, url).startswith("http"):
            url = urllib.parse.urljoin(self.baseurl, url)

...但这似乎只是引入了复杂性。

然后,它对打电话给 urllib.parse.urljoin :

实例py:24:错误:“urljoin”的类型变量“AnyStr”的值不能是“Sequence[object]”
实例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)

但这感觉像是一个很难解决的问题。

所以:

  • 我想我的函数签名是正确的 是的,但是上面有类型注释吗 url 对还是错 不正确并导致问题?

  • 周围的错误是怎么回事 urljoin ?


从评论中可以看出:

        if self.baseurl and not url.startswith(
            "http" if isinstance(url, str) else b"http"
        ):

失败于:

实例py:25:错误:“str”的“startswith”的参数1的类型“Union[str,bytes]”不兼容;应为“联合[str,元组[str,…]”
实例py:25:错误:“字节”的“startswith”的参数1的类型“Union[str,bytes]”不兼容;应为“联合[字节,元组[字节,…]”

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/128953
 
507 次点击  
文章 [ 1 ]  |  最新文章 2 年前
Grismar
Reply   •   1 楼
Grismar    2 年前

这就解决了整个问题:

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 如果你说你接受它。只要提出一个例外或者做一些更好的事情 字节 通过。甚至只是 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 反之亦然,它仍然无法验证。 麦皮 无法通过运行时逻辑进行验证,因此您只能执行以下操作:

    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)

这两者都支持,但以一种丑陋的方式重复了逻辑。