Python 開發者面向文檔編程的正確姿勢

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


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


Python


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


用註釋寫單元測試:doctest




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


一個巧妙的方式可以是通過doctest,用docstring註釋的方式來完成單元測試,由於每個方法def下面都先跟着一段測試用例,然後緊跟着就是代碼正文,這樣一來很方便我們測試現有代碼的質量,另一方面又便於修改。


舉個例子:


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也因此變得非常輕鬆。


用註釋寫API文檔:apidoc


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


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


舉個例子:


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


  • 路由

  • GET/POST方法

  • 名稱/分組

  • 參數與調用例子


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


"""

@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的官方文檔非常簡單清晰,所以這裏不過多強調語法。


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


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

  2. 要注意跨域的問題。


通過版本對比,我們還可以快速排查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)


歡迎大家留言討論,給出更多應用案例,交流分享。本文由健康大部落 std.jkdbl.com整理髮布

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章