Python 编码风格外谈

注:

  • 本文仅讨论 PEP8 规范未明确的细节, 并提供个人风格参考
  • 风格来自 Google Python Style Guide, 并根据个人喜好做了细微调整 (这些调整的目的是为了进一步提升视觉美感)

格式说明

推荐程度

# Good: 表示这是一个好的示范.

# Better: 表示这是一个更好的示范.

# Best: 表示这是一个最佳示范.

# Tricky: 表示这是一个更复杂的技巧, 它可以让代码变得更加精炼, 但也降低了易读性.

# Not Bad: 相对来说不算太好的做法, 您仍然可以将其作为一种示范. 通常来说这类做法不太严谨, 但胜在写法更加轻便, 而且仍然能保持形式美观.

# Not Good: 表示这是一个不太好的做法, 但不强烈拒绝你这么做 (如果你喜欢这种做法的话).

# Bad: 表示这是一个不好的做法, 你应该避免这样做.

# Worse: 表示这是一个更糟糕的做法, 你应该尽量避免这样做.

# Worst: 表示这是一个极其反面的示例, 在任何时候都应该拒绝这样做.

箭头 ^ 标记

print(a + b )
           ^
           这是一个位置标记, 它用于更明确地指出哪里出现了不规范的做法.
Lint: 对于不规范的做法, 我们会以 'Lint' 开头, 予以说明.

在这里插入图片描述

说明与提示

Note: 对这种做法予以详细的说明, 或者解释这样做的理由. 以 'Note' 开头.
Lint: 对不正确的做法予以提示, 指出哪里不正确/不合理. 以 'Lint' 开头.

风格详解

块注释

功能概述

# Good
def my_func():
    """ A one line summary of the function, terminated by a period. """
    Note: 开头和结尾要留一个空格.
          这样做的好处是, 与 Python 的 4 空格缩进风格保持一致, 使排版更整齐.


# Good
def my_func():
    """ This is a longer summary, its length exceeds the single line limit, so 
        we take a line break like this.
    """
    Note: 注意概述在换行时, 仍然保持与上一行相同的缩进.
          这也是为何之前建议开头和结尾要留空格的原因.


# Not Bad
def my_func():
    """ This is a longer summary, its length exceeds the single line limit, so 
        we take a line break like this. """
                                        ^-^
    Note: 当概述产生换行时, 将结尾的三引号留到末尾可作为另一种选择.


# Not Good
def my_func():
    """ A one line summary of the function, terminated by a period """
    Lint: 概述的末尾句点不建议被省略.                                 ^


# Bad
def my_func():
    """A one line summary of the function, terminated by a period."""
      ^                                                           ^
    Lint: 您应该在开头和结尾留一个空格的空间.

措辞风格

# Good
def get_rows():
    """ Fetches rows from a big table. """
        ^-----^
    Note: 使用声明式的措辞.


# Not Bad
def get_rows():
    """ Fetch rows from a big table. """
        ^---^
    Lint: 减少使用命令式的措辞.

详细描述

# Good
def my_func():
    """ A one line summary of the function, terminated by a period.
    
    Leave one blank line above. Here provides an overall description of this 
    module. Optionally, it may also contain usage examples and so on.
    """
    Note: 详细描述与概述之间留一个空行做缓冲.


# Not Bad
def my_func():
    """
    No blank line if there's no summary above, this is not often to use though.
    """
    Note: 当没有概述时, 可取消所留的空行, 这样使文本在适当的时候更紧凑, 仍然保持了形式美观.
    

# Not Good
def my_func():
    """ A one line summary of the function, terminated by a period.
    Leave one blank line above. Here provides an overall description of this 
    module. Optionally, it may also contain usage examples and so on.
    """
    Lint: 您应该在详细描述与概述之间留一个空行做缓冲.


# Bad
def my_func():
    """ A one line summary of the function, terminated by a period.
    
    Leave one blank line above. Here provides an overall description of this module. Optionally, it may also contain usage examples and so on.
    """
    Lint: 请不要让单行超过 80, 100120 字符. 该字符数限制取决于您的个人偏好和屏幕宽度.
          如果您在使用等宽中文字符 (例如等距更纱黑体), 那么可将一个汉字宽度看作两个字母看到.

参考或引用

class Parent:
    """ This is parent class.
    
    Args:
        name: A company or an organization name.
    """

class Child:
    """
    See `Parent` class.
    """
    Note: 通常, 参考 xxx 以 'See' 开头, 后跟要参考的对象.
          参考对象可以是其他类/方法/函数, 也可以是外部文档, 或者网络链接 (如下方示例).
    
    def draw(self):
        """ Draw a plot on the canvas. 
        
        See 'docs/samples/plot on canvas.png'.
            https://segmentfault.com/a/1190000015659597?utm_source=tag-newest
        """

特定字段

def list_files(idir, suffix=None, recursive=False):
    """ List out files under the directory.
    
    Args:
        idir (str): The entrance directory.
        suffix ([None|tuple]): The extension of filename, starts with a period.
            Example: ('.png', '.jpg', '.bmp')
        recursive (bool):
            True: Find all files, include files under subfolders.
            False: Find files only in current folder.
    
    Returns:
        [filepath, ...]
    
    Raises:
        FileNotFoundError: Please check if `idir` exists.
    """
    pass


class Person:
    """ Summary of class here.
    
    Attributes:
        name (str): Full name of person.
        age (int):
        gender (['male'|'female']): 
    
    Raises:
        AssertionError: The `age` is invalid.
        IndexError: The `gender` is undefined.
    """
    
    def __init__(self, name, age, gender: int):
        self.name = name
        self.age = age
        assert self.age > 0
        self.gender = ('male', 'female')[gender]

完整的特定字段清单如下:

OrderSectionNote
1References/Refer参考指向其他类/方法/函数, 也可以是外部文档, 或者网络链接.
1See相比 Refer 更宽松, 更自然的引用方式, 不需要严格的格式要求.
2Args列出每个参数名, 后跟空格 + 括号 + 类型 (可选), 然后跟冒号 + 描述 (可选).
2Attributes/Attr列出类的公共属性, 格式与 Args 相同.
3Example(s)/E.g.一个简短的示例, 可以插入在 Args 中 (在参数描述中换行并空 4 格书写), 或者单独作为一个 Section 出现.
3Usage(s)一个用法演示, 与 Example(s) 相比, 通常来说内容更完整, 情景更具体, 更易于让人举一反三的模仿. 且通常以多行的代码块形式出现.
3IO/In & Out当方法涉及文件输入输出时, 建议以此字段描述.
4Returns描述返回值.
4Yields描述迭代返回值.
5Raises列出可能产生的报错和错误的来源.

更多示例如下所示 (您可以看到它在实际使用中的效果, 注意示例中可能存在 “Not Bad” 的写法):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

行注释

单行注释

# Good
# This is a single line comment.
# The period at the end of comment is not necessary
Note: 末尾的句点是非必须的, 可以省略.


# Not Bad
# this is a single line comment
  ^
Note: 您可以选择全小写的风格, 如果这更偏向于您的喜好.

行尾注释

# Good
a = round(10 / 3, 2)  # got a percent number
b = f'{a}%'  # text format
Note: 行尾注释的第一个字母可以是大写, 也可以是小写. 取决于它是短语还是完整的句子, 但不需要刻意区分.
      您需要避免大小写混用, 这会让注释看起来比较杂乱. 通常在相近的语句块中坚持一种格式是好的.
      同样, 末尾的句点是非必须的, 可以省略.

对前一行注释

# Good
print(f'{unused_img_cnt / total_img_cnt * 100}%')
#   Tested in 2020-11-21, the percent is 44.61%, almost half of images are unused.
Note: 对于目标代码, 无论在它前一行写注释还是后一行写注释都很常见, 但对阅读来说并不方便, 因为无法立
      即分清它是指向上一行代码还是下一行.
      为了更清晰地表示指向上一行, 我们对行首的空格进行了调整. 确保注释内容的开始位置是 4 的整数倍
      (在 IDE 中按下 Tab 键, 可以很容易做到, 不需要我们数要敲击的空格数).

更多示例如下所示 (您可以看到它在实际使用中的效果, 确实能够提升易读性):

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

更多

本章节展示了更多适合强迫症的编程风格, 您可以根据自己的偏好有选择地采用.

变量命名习惯

对称好于不对称

注: 仅建议在局部范围尝试.

# == Good ==
benchmark = Benchmark()
select = Select()
provide = Provide()

# == Obsessive ==
bcmk = Benchmark()
slct = Select()
prvd = Provide()

# ------------------------------------------------

# == Good ==
class Foo:
    def fast_calc():
        pass
    def std_calc():  # std: standard
        pass

# == Obsessive 1 ==
class Foo:
    def fst_calc():  # fst: fast
        pass
    def std_calc():  # std: standard
        pass

# == Obsessive 2 ==
class Foo:
    def fast_calc():
        pass
    def stnd_calc():  # stnd: standard
        pass

语义明确好于语义简洁

# == Bad ==
er = ExcelReader()
ew = ExcelWriter()
fr = FileReader()
fw = FileWriter()

# == Good ==
reader1 = ExcelReader()
writer1 = ExcelWriter()
reader2 = FileReader()
writer2 = FileWriter()

# == Better ==
exl_reader = ExcelReader()
exl_writer = ExcelWriter()
file_reader = FileReader()
file_writer = FileWriter()

格式一致好于格式不一致

class MyFinder:

    def find_one(self, x):
        pass

    def find_last(self, x):
        pass

    # == Not Good ==
    def findall(self, x):
        pass

    # == Good ==
    def find_all(self, x):
        pass

使用 i-, o- (或者 r-, w-) 前缀表示 “输入”, “输出”

# (建议在局部变量使用)
for ifile, ofile in file_io.items():
    ilist = get_list_from_file(ifile)
    olist = handle_list(ilist)
    write_result(olist, ofile)

目录路径末尾不加斜杠

idir = '../data'
odir = '../output'

for name in name_list:
    ifile = f'{idir}/{name}.html'
    ofile = f'{odir}/{name}.json'
    ...

为什么?

显式的斜杠更易于阅读. 这点类似于百分号, 查看下面的例子:

# == Bad ==
def calc_percent_1(m, n):
    return str(round(m / n * 100, 2)) + '%'

# == Good ==
def calc_percent_2(m, n):
    return round(m / n * 100, 2)

print('当前的工作进度是 {}'.format(calc_percent_1(1, 3)))
print('当前的工作进度是 {}%'.format(calc_percent_2(1, 3)))

前者在易读性上更差一些. 特别是当计算百分比的函数和 print 在不同的 py 文件时, 前者更容易让人忽略掉它是百分数的事实. (而且 calc_percent_1 在函数 “单一职能” 的表达上也不如 calc_percent_2)

路径的斜杠的使用原则和这个百分号示例有着相同之处. 这是为什么不推荐在目录路径末尾加斜杠的原因.

附录

风格选型: 为什么 docstring 采用了基于 Google 的风格?

Python docstring 主流的有五种风格:

  1. Plain
  2. reStructuredText
  3. Numpy
  4. Google
  5. Epytext

下面仅从个人角度说一下对每个选择的考量:

  1. Plain 没有特定的风格指导, 不利于 pydoc 生成规范化的注释文档, 被排除
  2. reStructuredText (1) code fence 是用双 ``var`` 包裹, 与我的 Markdown 习惯 `var` 不一致; (2) 当手动补充参数时, 写起来比较麻烦 (比如 :param xxx: ..., 打得多了就感觉有些费事); (3) :param type xxx: ... 中的 type, Pycharm 似乎只能识别基本类型, 不能识别自定义类型; (4) :param xxx: ...\n:type xxx: 这样分开写也不太符合自己的喜好, 所以被排除
  3. Numpy 使用连续的短横线作为分隔符 (如下图), 个人感觉会让代码不太好看 (参数少了显得换行 “泛滥”, 参数多了各种长短不一的横线看起来杂乱), 而且多数中文字体的字宽不严格等于两个短横线, 这就导致换用字体可能引起短横线的长度与中文长度照不上的烦恼, 会让代码看起来更乱, 被排除
    在这里插入图片描述
  4. Epytext 没有深入了解过, 相较于其他风格有些偏冷门, 被排除

综上所述, Google 风格是个人先做排除法选中的, 再加上 Google 品牌效应, 良好严谨的 风格文档 (包括 中文文档), 以及社区的活跃和支持 (例如, Pycharm 的 autodoc 插件默认是以 Google 风格来解析的), 于是最终选择了它.

参考

  • Google Python Style Guide https://google.github.io/styleguide/pyguide.html
  • Python Docstring 风格和写法学习 - 飞鸟的笔记本 https://www.cnblogs.com/ryuasuka/p/11085387.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值