何时使用 `raise_for_status` 与 `status_code` 测试

Mad*_*vad 5 python error-handling python-3.x python-requests

我一直使用:

r = requests.get(url)
if r.status_code == 200:
    # my passing code
else:
    # anything else, if this even exists
Run Code Online (Sandbox Code Playgroud)

现在我正在处理另一个问题并决定允许其他错误,而是现在使用:

try:
    r = requests.get(url)
    r.raise_for_status()
except requests.exceptions.ConnectionError as err:
    # eg, no internet
    raise SystemExit(err)
except requests.exceptions.HTTPError as err:
    # eg, url, server and other errors
    raise SystemExit(err)
# the rest of my code is going here
Run Code Online (Sandbox Code Playgroud)

除了可以在此级别测试各种其他错误之外,一种方法是否比另一种方法更好?

Sam*_*gan 73

Response.raise_for_status()只是一个用于检查状态代码的内置方法,其作用与第一个示例基本相同。

这里没有“更好”,只是关于流量控制的个人喜好。我更喜欢使用 try/ except 块来捕获任何调用中的错误,因为这会通知未来的程序员这些条件是某种错误。if/else 不一定表示扫描代码时出现错误。


编辑:这是我的快速而肮脏的模式。

import time
from http import HTTPStatus

import requests
from requests.exceptions import HTTPError

url = "https://theurl.com"
retries = 3
retry_codes = [
    HTTPStatus.TOO_MANY_REQUESTS,
    HTTPStatus.INTERNAL_SERVER_ERROR,
    HTTPStatus.BAD_GATEWAY,
    HTTPStatus.SERVICE_UNAVAILABLE,
    HTTPStatus.GATEWAY_TIMEOUT,
]

for n in range(retries):
    try:
        response = requests.get(url)
        response.raise_for_status()

        break

    except HTTPError as exc:
        code = exc.response.status_code
        
        if code in retry_codes:
            # retry after n seconds
            time.sleep(n)
            continue

        raise
      
Run Code Online (Sandbox Code Playgroud)

然而,在大多数情况下,我会子类化requests.Session,创建一个HTTPAdapter处理指数退避的自定义,并且上面的内容存在于重写的requests.Session.request方法中。可以在这里看到一个例子。

  • 为了澄清,OP的第一个例子只接受HTTP 200作为成功条件,而第二个事实上接受所有HTTP代码<400(`raise_for_status()`在代码400-599上抛出异常),所以它是_非常粗略_“同一件事” 。 (4认同)
  • 我有点喜欢 try/ except 块,我只是发现当 2 或 3 个(或更多)嵌套时它们会变得混乱。对于嵌套的 if 块来说已经够糟糕了,但是多个 try/ except/ except/else/finally 块变得非常不可读。话虽如此,我越来越喜欢他们了:) (3认同)
  • 我同意。我不遗余力地尽可能减少嵌套。我的大量编码时间都花在了可读性上,“代码的读取次数比编写次数多”等等。我发现,如果嵌套或​​多个(超过 2 个)try/ except 块,或者确实存在任何深度嵌套/大型条件块,则这几乎总是触发重构。 (2认同)
  • (请求库[默认情况下遵循 HTTP 3xx 重定向(HEAD 除外)](https://docs.python-requests.org/en/master/user/quickstart/#redirection-and-history),因此 300-399在这两种情况下,代码的处理方式相同。) (2认同)

Ian*_*dby 30

几乎总是raise_for_status()更好。

主要原因是它比测试更多一些status_code == 200,您应该充分利用经过尝试和测试的代码,而不是创建自己的实现。

例如,您是否知道 HTTP 标准实际上定义了五种不同的“成功”代码?通过测试,其中四个“成功”代码将被误解为失败status_code == 200

  • 另一种观点:人们应该知道他们正在开发的目标是什么,而不是盲目地接受“任何”可能的成功代码。如果 API 文档说 HTTP 200 是预期的成功代码,那么这就是我要检查的内容。任何偏离此规定的行为均构成违约。 (9认同)
  • @Tobias 由于“违反合同”而拒绝回应可能是正确的做法,但其他时候[在你接受的内容上保持自由](https://en.wikipedia.org/wiki/Robustness_principle)是值得的。一如既往,上下文就是一切。 (3认同)

Gre*_*cki 17

如果您不确定,请遵循Ian Goldby 的回答

...但是请注意,这raise_for_status()并不是什么神奇或异常智能的解决方案 - 它是一个非常简单的函数,用于解码响应正文并抛出 HTTP 代码 400-599 的异常,区分客户端和服务器端错误(请参阅其代码在这里)。

特别是客户端错误响应可能在您可能想要处理的响应正文中包含有价值的信息。例如,HTTP 400 Bad Request 响应可能包含错误原因。

在这种情况下,最好不要使用raise_for_status(),而是自己覆盖所有情况。

示例代码

try:
    r = requests.get(url)

    # process the specific codes from the range 400-599
    # that you are interested in first
    if r.status_code == 400:
        invalid_request_reason = r.text
        print(f"Your request has failed because: {invalid_request_reason}")
        return
    # this will handle all other errors
    elif r.status_code > 400:
        print(f"Your request has failed with status code: {r.status_code}")
        return

except requests.exceptions.ConnectionError as err:
    # eg, no internet
    raise SystemExit(err)

# the rest of my code is going here
Run Code Online (Sandbox Code Playgroud)

现实世界的用例

PuppetDB 的 API 使用Puppet 查询语言 (PQL),以 HTTP 400 错误请求响应语法无效的查询,并提供非常精确的错误信息。

请求查询:

nodes[certname] { certname == "bastion" }
Run Code Online (Sandbox Code Playgroud)

HTTP 400 响应的正文:

PQL parse error at line 1, column 29:

nodes[certname] { certname == "bastion" }
                            ^

Expected one of:

[
false
true
#"[0-9]+"
-
'
"
#"\s+"
Run Code Online (Sandbox Code Playgroud)

请参阅我对使用此 API 的应用程序的 Pull 请求,以使其向用户显示此错误消息但请注意,它并不完全遵循上面的示例代码。

  • 是的,查看响应内部以获取有关问题所在的更多信息并使其可用于故障排除始终是一个好主意。同样的响应也可以通过 raise_for_status() 引发的异常获得 - `ex.response.status_code`、`ex.response.text` 等。 (4认同)