本文作者为 Noam Elfanbaum,是以色列的一名 Python 开发者。本文译者 linkmyth,校对 EarlGrey@编程派。linkmyth 是同济大学的在读硕士,主攻web开发、机器学习等方向。
本文参考了 Kenneth Reitz 的 Requests 库 的 API 设计。
编写软件包(库)时,设计良好的 API 与软件包的功能同样重要(当然,前提是你想让别人使用),那么好的 API 的标准是什么?在本文中,笔者将会比较 Requests 库和 Urllib 库(属于 Python 标准库)在一些典型的 HTTP 使用场景下的差异,并依此发表一些笔者的看法,同时讨论一下 Requests 库为何在 Python 用户群中成为实际上的标准库。
接下来的讨论中我们将会用到 Python 3.5 和 Requests 2.10.0 。
这篇文章改编自上周我在本地的 Python 聚会上的 演讲 。读者可以在 这里 找到演讲的幻灯片。
import urllib.request urllib.request.urlopen('http://python.org/')
<http.client.HTTPResponse at 0x7fdb08b1bba8>
import requests requests.get('http://python.org/')
<Response [200]>
data
参数的情况下发送 Get 请求,这种方式要更加隐晦 __repr()__
方法实现的) requests/api.py :
def request(method, url, **kwargs): with sessions.Session() as session: return session.request(method=method, url=url, **kwargs) def get(url, params=None, **kwargs): kwargs.setdefault('allow_redirects', True) return request('get', url, params=params, **kwargs) def post(url, data=None, json=None, **kwargs): return request('post', url, data=data, json=json, **kwargs)
request()
函数作为主要的流程控制函数。 request()
函数,这使得我们的函数调用更加明确。 import urllib.request r = urllib.request.urlopen('http://python.org/') r.getcode()
import requests r = requests.get('http://python.org/') r.status_code
@property
装饰器就能完成这一目标。 http/client.py :
class HTTPResponse(io.BufferedIOBase): # ... def getcode(self): return self.status
import urllib.parse import urllib.request import json url = 'http://www.httpbin.org/post' values = {'name' : 'Michael Foord'} data = urllib.parse.urlencode(values).encode() response = urllib.request.urlopen(url, data) body = response.read().decode() json.loads(body)
import requests url = 'http://www.httpbin.org/post' data = {'name' : 'Michael Foord'} response = requests.post(url, data=data) response.json()
同样地,Requests 库也为发送 JSON 数据提供了一种优雅的方式:
import requests url = 'http://www.httpbin.org/post' data = {'name' : 'Michael Foord'} response = requests.post(url, json=data) response.json()
下面的代码为 HTTP 请求完成了长期的身份认证,同时发送了一个请求:
import urllib.request gh_url = 'https://api.github.com/user' password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, gh_url, 'user', 'pswd') handler = urllib.request.HTTPBasicAuthHandler(password_mgr) opener = urllib.request.build_opener(handler) opener.open(gh_url)
import requests session = requests.Session() session.auth = ('user', 'pswd') session.get('https://api.github.com/user')
但是如果我们只需完成一次 HTTP 请求?是否还需要这么多代码?使用 Requests 库只需要下面的代码就可以完成:
import requests requests.get('https://api.github.com/user', auth=('user', 'pswd'))
requests/models.py
def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" # ... if auth: if isinstance(auth, tuple) and len(auth) == 2: # special-case basic HTTP auth auth = HTTPBasicAuth(*auth)
(user, pass)
元组转化为一个身份验证类 from urllib.request import urlopen response = urlopen('http://www.httpbin.org/geta') response.getcode()
--------------------------------------------------------------------------- HTTPError Traceback (most recent call last) <ipython-input-45-5fba039d189a> in <module>() 1 from urllib.request import urlopen ----> 2 response = urlopen('http://www.httpbin.org/geta') 3 response.getcode() /usr/lib/python3.5/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context) 161 else: 162 opener = _opener --> 163 return opener.open(url, data, timeout) 164 165 def install_opener(opener): /usr/lib/python3.5/urllib/request.py in open(self, fullurl, data, timeout) 470 for processor in self.process_response.get(protocol, []): 471 meth = getattr(processor, meth_name) --> 472 response = meth(req, response) 473 474 return response /usr/lib/python3.5/urllib/request.py in http_response(self, request, response) 580 if not (200 <= code < 300): 581 response = self.parent.error( --> 582 'http', request, response, code, msg, hdrs) 583 584 return response /usr/lib/python3.5/urllib/request.py in error(self, proto, *args) 508 if http_err: 509 args = (dict, 'default', 'http_error_default') + orig_args --> 510 return self._call_chain(*args) 511 512 # XXX probably also want an abstract factory that knows when it makes /usr/lib/python3.5/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args) 442 for handler in handlers: 443 func = getattr(handler, meth_name) --> 444 result = func(*args) 445 if result is not None: 446 return result /usr/lib/python3.5/urllib/request.py in http_error_default(self, req, fp, code, msg, hdrs) 588 class HTTPDefaultErrorHandler(BaseHandler): 589 def http_error_default(self, req, fp, code, msg, hdrs): --> 590 raise HTTPError(req.full_url, code, msg, hdrs, fp) 591 592 class HTTPRedirectHandler(BaseHandler): HTTPError: HTTP Error 404: NOT FOUND
import requests r = requests.get('http://www.httpbin.org/geta') r.status_code
用例:
from urllib.request import urlopen from urllib.error import URLError, HTTPError try: response = urlopen('http://www.httpbin.org/geta') except HTTPError as e: if e.code == 404: print('Page not found') else: print('All good')
Page not found
from requests.exceptions import HTTPError import requests r = requests.get('http://www.httpbin.org/posta') try: r.raise_for_status() except HTTPError as e: if e.response.status_code == 404: print('Page not found')
Page not found
import requests r = requests.get('http://www.httpbin.org/geta') if r.ok: print('All good') elif r.status_code == requests.codes.not_found: print('Page not found')
Page not found
以上所述就是这篇文章的全部内容。在准备这次演讲和这篇文章的过程中,笔者收获颇丰,也希望读者在阅读的过程中同样能有所收获。读者可以通过在文章下方评论或者在 Twitter 上留言的方式(@noamelf)向我提供建议,我非常乐于倾听这些建议。
如果读者在阅读完文章后,像包括笔者在内的很多人一样,对 Requests 库和 Urllib 库的可用性之间存在如此大的差异感到惊讶,那么 Nick Coghlan 在 下面的评论 和后来的文章(标题的含义一目了然) 它解决了什么问题 中分享了自己对这个问题的看法。
点此查看原文链接 。
Python 翻译组 是EarlGrey@编程派发起成立的一个专注于 Python 技术内容翻译的小组,目前已有近 30 名 Python 技术爱好者加入。
翻译组出品的内容(包括教程、文档、书籍、视频)将在编程派微信公众号首发,欢迎各位 Python 爱好者推荐相关线索。
推荐线索,可直接在编程派微信公众号推文下留言即可。