[原] 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)

欢迎你们留言讨论,给出更多应用案例,交流分享。

参考文献

相关文章
相关标签/搜索