美文网首页
Python数据类型提示痛点的解决方案探讨

Python数据类型提示痛点的解决方案探讨

作者: m2fox | 来源:发表于2019-04-07 22:14 被阅读0次

参考:
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代码提供完备、清晰的文档注释,但提升代码的可读性可以先从自身做起,起码可以防止那种自己也读不懂自己的代码、只有上帝才能看懂的尴尬局面的发生。

相关文章

网友评论

      本文标题:Python数据类型提示痛点的解决方案探讨

      本文链接:https://www.haomeiwen.com/subject/keieiqtx.html