Python 編程理念的應用編程介面詳解

來源:互聯網
上載者:User


編寫軟體包(庫)時,設計良好的 API 與軟體包的功能同樣重要(當然,前提是你想讓別人使用),那麼好的 API 的標準是什嗎?在本文中,筆者將會比較 Requests 庫和 Urllib 庫(屬於 Python 標準庫)在一些典型的 HTTP 使用情境下的差異,並依此發表一些筆者的看法,同時討論一下 Requests 庫為何在 Python 使用者群中成為實際上的標準庫。

接下來的討論中我們將會用到 Python 3.5 和 Requests 2.10.0。

這篇文章改編自上周我在本地的 Python 聚會上的演講。讀者可以在這裡找到演講的投影片。

Requests 和 Urllib

用例 1:發送 Get 請求

import urllib.request
urllib.request.urlopen('http://python.org/')
<http.client.HTTPResponse at 0x7fdb08b1bba8>
import requests
requests.get('http://python.org/')
<Response [200]>
明確(API 端點)優於隱晦

Requests 庫發送請求的目的更加簡明(因此也更加清晰)
Urllib 庫是在省略data參數的情況下發送 Get 請求,這種方式要更加隱晦
Requests 庫的函數名清晰地解釋了函數的用途
有用的對象標記法

讀者觀察後可以發現,Requests 庫返回一個包含請求狀態代碼的字串(這是通過__repr()__方法實現的)
Urllib 庫只返回預設的(模糊的)對象表示
程式碼片段

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)
所有的 HTTP 動作在發送之前都會有類似的處理流程,因此這裡實現一個request()函數作為主要的流程式控制制函數。
所有的 HTTP 動作都有一個對應的“輔助函數”,然後在輔助函數中調用request()函數,這使得我們的函數調用更加明確。
用例 2:擷取請求狀態代碼

import urllib.request
r = urllib.request.urlopen('http://python.org/')
r.getcode()
200
import requests
r = requests.get('http://python.org/')
r.status_code
200
不需要 getters 和 setters

通過讀取屬性的方式(而不是調用方法)擷取對象的特效能使代碼更加清晰。
如果讀者接觸過其他物件導向的語言(比如說 Java),你可能會通過設定 getters 和 setters 來修改對象的屬性。在 Python 中則不必如此,讀者只需使用 @property裝飾器就能完成這一目標。
程式碼片段

http/client.py:

class HTTPResponse(io.BufferedIOBase):

    # ...

    def getcode(self):
        return self.status
Urllib 庫(或者說 http)用一個“getter”方法返回類的屬性
用例 3:編碼、發送和解碼 POST 請求

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 響應,然而讀者在使用 Urllib 庫時需要自己實現這些方法。
在設計 API 時讀者需要思考:軟體包最主要的用途是什嗎?可以添加哪些介面能更方便的滿足這些用途?
同樣地,Requests 庫也為發送 JSON 資料提供了一種優雅的方式:

import requests

url = 'http://www.httpbin.org/post'
data = {'name' : 'Michael Foord'}

response = requests.post(url, json=data)
response.json()
用例 4:發送驗證過的請求

下面的代碼為 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 庫既有發送單個請求的簡單用法,也擁有發送多個請求的複雜用法。
不要讓使用者在完成簡單任務時也需要經過漫長的過程。
盡量使用 Python 內建的資料結構,而不是建立新的資料結構

Requests 庫使用 Python 內建的資料結構,這使得它非常便於使用。使用者不需要瞭解 Requests 庫內部的結構。
庫代碼

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)
Requests 庫在內部將 (user, pass) 元組轉化為一個身分識別驗證類
用例 5:處理錯誤

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
404
讓使用者選擇處理錯誤的方式

有些程式員傾向於用異常的方式處理錯誤,而有些則傾向於用檢查的方式處理錯誤。
某些情境下檢查的方式更為優雅,而另一些情境下則恰恰相反。
合理的做法是讓使用者可以選擇處理錯誤的方式。
預設返回錯誤碼可以實現上面所述的合理做法,而預設採用異常處理錯誤則無法實現。
用例:

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)向我提供建議,我非常樂於傾聽這些建議

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.