[原] Python 開發者面向文檔編程的正確姿式

概述

clipboard.png

秦人不暇自哀,然後人哀之;後人哀之而不鑑之,亦使後人而復哀後人也! --論面向文檔編程的重要性html

若是想看見識一我的寫代碼的功力,註釋實際上是區分老司機和小鮮肉的一個顯著的分界線(有沒有觀察到大家公司的領導基本都在開會或者寫文檔),一般狀況下老司機的文檔量與代碼量是1:1的比例,而新人每每認爲寫完功能模塊就已經能夠完成任務了。生產環境中須要面對現實中大量複雜的業務邏輯和數據校驗並與各方對接,文檔質量和代碼質量就被提高到了相同的高度。不少人沒有寫註釋的習慣,大多數不是由於懶惰,一方面是沒有意識到寫文檔的好處,另外一方面是不瞭解這方面的工具。畢竟從管理上依賴於人的主動性是遠不如依賴於工具備效的。本文介紹如何利用Python註釋提高文檔書寫的質量以及效率的小技巧。python

Python

在實際生產中,機器學習工做如今看起來,白天像是個算法工程師的活,晚上就變成運維+測試了。Python 一直以來也都受到測試工程師和運維工程師的偏心,下面是幾個經典的註釋活用case。git

用註釋寫單元測試:doctest

clipboard.png

單元測試是代碼開發環節必不可少的一環,對於Bug定位和代碼質量而言是很是重要的。如今最廣爲人知的單元測試框架就是Unittest,它借鑑了Java中成熟的單元測試框架的JUnit。即便像Django還對這個框架有特殊的支持,然而在實現Unittest的時候會感受確實比較囉嗦,setup,teardown...在維護單元測試的時候不少時候感受力不從心。github

一個巧妙的方式能夠是經過doctest,用docstring註釋的方式來完成單元測試,因爲每一個方法def下面都先跟着一段測試用例,而後緊跟着就是代碼正文,這樣一來很方便咱們測試現有代碼的質量,另外一方面又便於修改。web

舉個例子:算法

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(30)
    265252859812191058636308480000000
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    >>> factorial(30.1)
    Traceback (most recent call last):
        ...
    ValueError: n must be exact integer
    >>> factorial(30.0)
    265252859812191058636308480000000

    It must also not be ridiculously large:
    >>> factorial(1e100)
    Traceback (most recent call last):
        ...
    OverflowError: n too large
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result


if __name__ == "__main__":
    import doctest
    doctest.testmod()

上面是官網提供的一個求N的階乘函數示例,在docstring 中經過 >>>符號來開始一個單元測試,以後換行輸入預期結果便可。實際上就是複製粘貼一下調試過程和結果,真的再簡單不過了,想實現TDD也所以變得很是輕鬆。npm

用註釋寫API文檔:apidoc

clipboard.png

在咱們完成機器學習模型後,想要提供一個對外服務的接口以貢獻咱們的算力時就須要完備的API文檔,也是經過API的調用才能爲咱們的模型提供源源不斷的校驗數據,對於提高模型效果有很是實際的意義。對大多數人而言調用API來完成開發都是一件比較開心的事情,由於咱們能夠少作不少工做就能夠實現強大功能。然而,當咱們須要對外提供API時就要面臨不同的考驗了,接口鑑權、接口設計、版本控制、併發問題、日誌埋點...這些都是須要面對的新問題,而利用 apidoc 能夠很好地解決這些API文檔中常見的諸多問題,至關於經過模板提高了咱們的接口設計的能力。編程

apidoc爲Python提供了一種相似於 docstring 的方式來寫API文檔,從語法上看比較相似於 R中的roxygen,都須要用戶以 @xxx 符號做爲一個開頭,隨後書寫相關的定義和功能。json

舉個例子api

下面是一個API接口的定義方法,最核心的部分就是

  1. 路由

  2. GET/POST方法

  3. 名稱/分組

  4. 參數與調用例子

(這年頭沒有用例的代碼都是耍流氓)

"""
@api {get} /user/:id Request User information
@apiName GetUser
@apiGroup User

@apiParam {Number} id Users unique ID.

@apiSuccess {String} firstname Firstname of the User.
@apiSuccess {String} lastname  Lastname of the User.
"""

咱們能夠直接擼一個官方示例來學習如何使用apidoc。

首先,下載示例源碼

git clone https://github.com/apidoc/apidoc
cd apidoc

而後,安裝 apidoc 組件

sudo npm install apidoc -g

接着,利用官方代碼來製做一個例子,而且訪問便可。

apidoc -i example/ -o output/ -t template/
open output/index.html

幾個參數的含義以下:
-i:input,表示輸入的文件夾
-o:output,表示輸出文件夾
-t:template,表示模板文件,經過替換模板咱們能夠修改文檔皮膚

在 example 文件夾下,咱們須要在apidoc.json 中填寫配置文件,定義文檔的header和footer部份內容,其他的文件會被自動識別出其中的docstring做爲API文檔的一部分。

因爲apidoc官方文檔很是簡單清晰,因此這裏不過多強調語法。

clipboard.png

apidoc 還爲咱們提供了接口調試的功能,在實際使用的時候要注意:

  1. 咱們須要一個web server 纔可使用這個接口調試的功能

  2. 要注意跨域的問題。

clipboard.png

經過版本對比,咱們還能夠快速排查API接口的變化狀況。須要注意的是這個功能要求咱們要將歷史的文檔記錄也要保存在該目錄下的文件中,一般咱們能夠把歷史的註釋輸出到一個特定文件中保存。

總的來講,雖然,API文檔的書寫並非一件難度很是高的事情,卻能體現系統模塊設計和用戶體驗設計的功力,咱們應該對那些無代碼示例,無版本控制的API文檔say no!

用註釋寫命令行接口docopt

利用docopt,咱們能夠在註釋中直接聲明文件的命令行傳入參數,而不須要經過 argvs變量來捕獲輸入值再作判斷,這在調用運維腳本或者若干任務調度腳本的時候尤爲管用,極大地提高了CLI的效率。

舉個例子:(此處代碼僅供參考)

"""Usage:
  fiannceR.py tcp <host> <port> [--timeout=<seconds>]
  fiannceR.py serial <port> [--baud=9600] [--timeout=<seconds>]
  fiannceR.py -h | --help | --version

"""
from docopt import docopt

if __name__ == '__main__':
    arguments = docopt(__doc__, version='0.1.1rc')
    print(arguments)

隨後,咱們能夠在命令行中成功調用

fiannceR.py tcp 0.0.0.0 3838

這裏的 arguments 將傳出一個字典對象,以Key-Value的形式將命令行中的輸入值捕獲。

{'--baud': None,
 '--help': False,
 '--timeout': None,
 '--version': False,
 '-h': False,
 '<host>': '0.0.0.0',
 '<port>': '3838',
 'serial': False,
 'tcp': True}

總結

若是真的要從數據擼到模型、接口,那麼一排註釋的畫面真是美得不敢想象。

"""unitest
>>> FinanceR('20161001')
21.01
"""
def FinanceR(date):
    price = get_price(date)
    return(price)

class(BaseHandler):
    def get(self):   
        """apidoc
           @api {get} /price/:date 獲取當前價格
           @apiName GetPrice
           @apiGroup Quota

           @apiParam {Number} date 交易日期

           @apiSuccess {String} price
        """
        date = self.get_argument('date',None)
        try:
            price = FinanceR(date)
            self.write({'data':{'price':price},'response':{'message':'success','code':200}})
        except Exception as e:
            self.write({'data':None,'response':{'message':str(e),'code':404}})
            
"""Usage:
  fiannceR.py tcp <host> <port> [--timeout=<seconds>]
  fiannceR.py serial <port> [--baud=9600] [--timeout=<seconds>]
  fiannceR.py -h | --help | --version

"""
from docopt import docopt

if __name__ == '__main__':
    arguments = docopt(__doc__, version='0.1.1rc')
    print(arguments)

歡迎你們留言討論,給出更多應用案例,交流分享。

參考文獻

相關文章
相關標籤/搜索