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

Posted

技术标签:

【中文标题】何时使用 `raise_for_status` 与 `status_code` 测试【英文标题】:When to use `raise_for_status` vs `status_code` testing 【发布时间】:2020-08-11 06:20:50 【问题描述】:

我一直用:

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

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

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

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

【问题讨论】:

由于异常名称而不是状态码,第二个可能更具可读性,但我认为第一个更简单,您可以编写有关状态码含义的 cmets。 @EkremDİNÇEL 我倾向于这种方式,因为在这种情况下,我使用的系统并不总是有互联网连接,需要添加测试,坦率地说,我更喜欢这种方式这看起来:) 【参考方案1】:

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

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

【讨论】:

我有点喜欢 try/except 块,我只是发现当 2 或 3(或更多)嵌套时它们会变得混乱。对于嵌套的 if 块来说已经够糟糕了,但是多个 try/except/except/else/finally 块变得非常不可读。话虽如此,我越来越喜欢他们了:) 我同意。我会尽可能地减少嵌套。我的大量编码时间都用于可读性,“代码的阅读次数多于编写次数”等等。我发现如果嵌套或​​多个(超过 2 个)try/except 块,或者确实存在任何深度嵌套/大型条件块,这几乎总是触发重构。 澄清一下,OP 的第一个示例仅接受 HTTP 200 作为成功条件,而第二个事实上接受所有 HTTP 代码 raise_for_status() 在代码 400-599 上抛出异常),所以它是 非常粗略“同样的事情”。 (请求库follows the HTTP 3xx redirects by default (except for HEAD),所以300-399代码在两种情况下的处理方式相同。)【参考方案2】:

如果您不确定,请关注Ian Goldby's answer。

...但是请注意,raise_for_status() 并不是什么神奇或特别聪明的解决方案——它是一个非常简单的函数,可以解码响应正文并抛出 HTTP 代码 400-599 的异常,区分客户端和服务器-side 错误(参见其代码here)。

尤其是客户端错误响应可能包含您可能想要处理的响应正文中的有价值信息。例如,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

实际用例

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

请求查询:

nodes[certname]  certname == "bastion" 

HTTP 400 响应的正文:

PQL parse error at line 1, column 29:

nodes[certname]  certname == "bastion" 
                            ^

Expected one of:

[
false
true
#"[0-9]+"
-
'
"
#"\s+"

查看我对使用此 API 的应用的拉取请求,使其向用户 here 显示此错误消息,但请注意,它并不完全遵循上面的示例代码。

【讨论】:

【参考方案3】:

更好有点主观;两者都可以完成工作。也就是说,作为一个相对缺乏经验的程序员,我更喜欢Try / Except 形式。 对我来说,T / E 提醒我请求并不总是给你所期望的(在某种程度上 if / else 没有 - 但那可能就是我)。

raise_for_status() 还允许您根据需要轻松为不同的错误类型(.HTTPError.ConnectionError)实施尽可能多或尽可能少的不同操作。 在我目前的项目中,我已经确定了下面的表格,因为我正在采取同样的行动,无论原因如何,但我仍然有兴趣知道原因:

    try:
        ...
    except requests.exceptions.RequestException as e:
        raise SystemExit(e) from None

玩具实现:

import requests

def http_bin_repsonse(status_code):
    sc = status_code
    try:
        url = "http://httpbin.org/status/" + str(sc)
        response = requests.post(url)
        response.raise_for_status()

        p = response.content

    except requests.exceptions.RequestException as e:
        print("placeholder for save file / clean-up")
        raise SystemExit(e) from None
    
    return response, p

response, p = http_bin_repsonse(403)
print(response.status_code)

【讨论】:

【参考方案4】:

几乎总是,raise_for_status() 更好。

主要原因是除了测试status_code == 200 之外,还有更多内容,您应该充分利用经过验证的代码,而不是创建自己的实现。

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

【讨论】:

以上是关于何时使用 `raise_for_status` 与 `status_code` 测试的主要内容,如果未能解决你的问题,请参考以下文章

NoSQL与RDBMS:何时使用,何时不使用

何时使用 json 以及何时使用 jsonp 与 jquery $.ajax?

Twig 渲染与包含 - 何时何地使用其中一个?

何时使用 WPF 依赖属性与 INotifyPropertyChanged

何时在 TypeScript 中使用类与模块?

何时使用 LocalRedirect 与 RedirectToPage