参考:
PEP 484 Type Hints:https://www.python.org/dev/peps/pep-0484/
numpy项目代码文档注释样例:https://github.com/numpy/numpy/blob/master/numpy/matrixlib/defmatrix.py
几个月前,你写了一段Python代码,当时只有你和上帝能看懂。几个月后,这段代码就只有上帝能看懂了。
痛点是什么
Python是一门弱类型的动态语言,在看其他人写的一些Python项目的代码、特别是大型项目的代码的时候,是不是会有这样的体验:很多函数的参数超多,而且很难看出来每个参数代表什么含义、是什么数据类型的、字典类型的参数究竟可以传哪些参数等等等。如果写这些代码的人习惯还不好,没有写什么注释或注释写的比较糟糕,那看这种代码简直就是一种折磨了,这样的情况下一开篇所描述的情景就并不算夸张了。这样的代码可读性、可维护性几乎为0。
解决方案
PEP 484类型提示语法
那么怎么解决上述痛点呢?Python 3.5版本在语言层面加入了一个新特性:类型提示(Type Hints,详情见开篇引文),可以以一种非常优雅的方式来对代码中变量的类型进行提示,大大提高了Python代码的可读性,例如下面这段代码:
def hello(name:str, age:int) -> str:
return 'hello, {0}! your age is: {1}'.format(name, age)
在IPython中输入这段代码,然后用help
函数查看hello
函数的帮组文档,显示内容如下:
In [10]: help(hello)
Help on function hello in module __main__:
hello(name: str, age: int) -> str
类型提示语法中,在变量或函数形参后面加:类型名称
可以指明变量的数据类型,在函数定义def语句的后面加上-> 类型名称
可以指明函数返回值的数据类型,非常方便。
PEP 484类型提示的不足之处
要注意的是,PEP 484的类型提示语言特性只在Python 3.5或更高版本才支持,而且仅仅是作为注释性的语法元素进行解析,在编译的时候类型提示相关的代码都是会被当做注释而忽略掉的,Python编译器并不会因为有了类型提示而对代码做任何类型检查。
更通用的解决方案:文档注释
那么还有没有更好、更通用的办法来解决上述类型提示痛点呢?有的,文档注释就是一个非常好的解决办法。
文档注释的优点
- 清晰、准确、完善的文档注释可以让Python代码的可读性、可维护性非常高。
- 不受Python语言版本的限制。
- 按照一定约定规范书写的文档注释,可以被一些第三方IDE或语法检查工具利用,从而可以对代码做静态类型检查,在语法层面检查代码是否有类型错误,也可以做一些代码补全方面的事情。
PyCharm风格的文档注释
这是一种个人比较推崇的文档注释格式,PyCharm IDE可以识别这种文档注释格式,从而进行代码静态语法类型检查、代码自动补全等操作。
下面是一个简单的示例,在示例中用文档注释标注了hello
函数的功能描述、每个形参变量的含义和类型、函数的返回值类型等信息:
def hello(name, age, habits = []):
'''
Say hello to someone, and print some info.
:param name: name of someone
:type name: str
:param age: age of someone
:type age: int
:param habits: habits of someone
:type habits: List[str]
:return: a hello sentence
:rtype: str
'''
return 'Hello, {name}! Your age is {age}, and your habits are {habits}.'.format(name = name, age = age, habits = habits)
将上述代码输入到IPython中,然后执行hello?
命令来查看hello
函数的文档注释,结果如下:
In [28]: hello?
Signature: hello(name, age, habits=[])
Docstring:
Say hello to someone, and print some info.
:param name: name of someone
:type name: str
:param age: age of someone
:type age: int
:param habits: habits of someone
:type habits: List[str]
:return: a hello sentence
:rtype: str
可以看出函数的功能描述、每个参数的含义和数据类型、函数返回值的含义和数据类型都非常清晰明了。
numpy风格的文档注释
还有一种numpy项目中所使用的文档注释风格,也非常不错,可以参考。一个示例如下:
def squeeze(self, axis=None):
"""
Return a possibly reshaped matrix.
Refer to `numpy.squeeze` for more documentation.
Parameters
----------
axis : None or int or tuple of ints, optional
Selects a subset of the single-dimensional entries in the shape.
If an axis is selected with shape entry greater than one,
an error is raised.
Returns
-------
squeezed : matrix
The matrix, but as a (1, N) matrix if it had shape (N, 1).
See Also
--------
numpy.squeeze : related function
Notes
-----
If `m` has a single column then that column is returned
as the single row of a matrix. Otherwise `m` is returned.
The returned matrix is always either `m` itself or a view into `m`.
Supplying an axis keyword argument will not affect the returned matrix
but it may cause an error to be raised.
Examples
--------
>>> c = np.matrix([[1], [2]])
>>> c
matrix([[1],
[2]])
>>> c.squeeze()
matrix([[1, 2]])
>>> r = c.T
>>> r
matrix([[1, 2]])
>>> r.squeeze()
matrix([[1, 2]])
>>> m = np.matrix([[1, 2], [3, 4]])
>>> m.squeeze()
matrix([[1, 2],
[3, 4]])
"""
return N.ndarray.squeeze(self, axis=axis)
Emacs编辑器自动插入PyCharm风格的文档注释模板
如果你平时经常使用Emacs编辑器写Python代码,那么我写了一个简单的函数,可以方便地在python-mode
一键插入上面所讲的PyCharm风格的文档注释。只需将下面的elisp代码加入你的Emacs配置文件中,然后在python-mode
写完一个函数的def
语句后,光标在def
语句所在的行时按快捷键C-c C-a
即可快速插入文档注释模板:
(defun insert-doc-annotation-below-current-line ()
"Insert doc annotations for python class or function below current line."
(interactive)
;; first, get current line text
(let ((cur-line-str (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
(cond
;; judge whether current line text match python function define pattern
((string-match "^[[:space:]]*def.+?(\\(.*\\))[[:space:]]*:" cur-line-str)
;; first capture group of regex above is params list string
(setq params-str (match-string 1 cur-line-str))
;; split params list string to list, and do some strip operation
(setq params (split-string params-str ",[[:space:]]*" t "[[:space:]]*=.+?"))
;; go to end of current line and go to new line and indent
(goto-char (line-end-position))
(newline-and-indent)
;; insert head of doc annotation `'''`
(insert "'''")
(goto-char (line-end-position))
(newline-and-indent)
;; record current line number, jump back to here after inserting annotation
(setq annotation-top-line (line-number-at-pos))
(newline-and-indent)
(newline-and-indent)
;; insert each params annotation
(while params
;; NOT insert param `self`
(when (not (string-equal (car params) "self"))
(progn
(setq param (car params))
;; insert param name annotation line
(insert (format ":param %s: " param))
(newline-and-indent)
;; insert param type annotation line
(insert (format ":type %s: " param))
(newline-and-indent)
(newline-and-indent)))
(setq params (cdr params)))
;; insert return and return type annotation line
(insert ":return: ")
(newline-and-indent)
(insert ":rtype:")
(newline-and-indent)
;; insert tail of doc annotation
(insert "'''")
;; jump back to the position of annotation top
(goto-line annotation-top-line)
(indent-for-tab-command))
((string-match "^[[:space:]]*class.+?:" cur-line-str)
(goto-char (line-end-position))
(newline-and-indent)
;; insert head of doc annotation `'''`
(insert "'''")
(goto-char (line-end-position))
(newline-and-indent)
;; record current line number, jump back to here after inserting annotation
(setq annotation-top-line (line-number-at-pos))
(newline-and-indent)
;; insert tail of doc annotation
(insert "'''")
;; jump back to the position of annotation top
(goto-line annotation-top-line)
(indent-for-tab-command))
(t (message "current line NOT match neither function nor class!")))))
(add-hook 'python-mode-hook
(lambda ()
(local-set-key (kbd "C-c C-a") 'insert-doc-annotation-below-current-line)))
总结
最终决定采用文档注释这种更通用的方式在Python代码中做类型提示,虽然无法做到要求其他的人都能为他们自己的Python代码提供完备、清晰的文档注释,但提升代码的可读性可以先从自身做起,起码可以防止那种自己也读不懂自己的代码、只有上帝才能看懂的尴尬局面的发生。
网友评论