wKevin
wKevin

Categories

Tags

出品方 代码风格 美化工具
Python PEP8
网友译, 本人译
Black
Google Google Python Style
网友译
YAPF

代码风格(Code Style)与编码规范还是有些差异,我以为:

  • 代码风格 主要关注代码排版、一致性、易读性 —— 至美
  • 编码规范 概念更宏观,范畴更广泛,包括设计理念、性能优化、代码风格 —— 至真、至善、至美

PSF & PEP

PSF(Python Software Foundation),Python的官方组织。PSF 提供的文档包括:

PEP 是 PSF 写出、收纳、提供给社区参考使用、包含新特性、规范性等内容的说明文档。

PEP在github上撰写、管理,自动编译html、同步到网站上,从2000年开始创建,至今(2019.5)仍非常活跃,如果是python的developer,建议star或watch它,实时跟踪。

PEP分了8类:核心类、已提交、已接收、讨论中、讨论完、归档、拒绝、废弃

  • 有些pep很庞大,如:pep8(python代码风格);也有些很简短,如:pep7(c代码风格)
  • 有些pep很一本正经,如:pep569(Python 3.8 Release Schedule);有些也很搞怪,如:pep20(Zen of Python,20条禅学)

要贡献 PEP,需要参考 pep12 的模板来写,并且 PEP 不是用 markdown 写的,而是 ReStructuredText,也是标记(markup)语言,语法比 markdown 稍复杂一点点,这里有一些语法说明:

普通 Python 开发人员必读的PEP有:

  • 代码风格
    • PEP8:Python 代码风格
    • PEP7:C 编码规范
  • 文档规范
    • PEP256:文档系统工作机制
    • PEP257:docstrings Conventions
    • PEP258:注释与文档转换工具
    • PEP287:reStructuredText 书写规则

PEP8 Style Guide for Python Code

核心类 pep 是 PEPs of PEPs,目前只有9个,PEP8 位列其中,内容是 Python 的代码风格(Code Style),Python开发人员必读文档之一。

PEP8 从2001年创建,至今有141次提交:

修改(提交)集中在2002、 2012~2016年,2019已相对稳定:

参与撰写的人不少,但top3都是python官方的人:

PEP8 极简版

PEP8 有网友翻译:python代码风格指南(PEP8中文版),目前最后更新时间2018.8。

下面我来根据2019.5的PEP8,整理一份极简版,相比上面的网友翻译,增补了:

  • Module级dunder
  • 尾随逗号

极简版正文:

写“可读的代码”

代码的使用频率上看:读取远大于编写,所以一定要写“可读的代码”

每级缩进4空格,不允许tab和空格混用

入参和if分开讨论

# 函数定义的入参比函数名多1级缩进
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# 函数调用的入参可对准左括号 or 悬挂缩进1级
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)

# if条件语句跨行时缩进:加1级即可
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

最大行宽79字符,文本块 72 字符,极限 99 字符

  • 以前通常在二元运算符(+-*/ and or…)之后续行
  • 现在推荐在二元运算符之前续行
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

空行要尽量节约使用

  • 顶层函数和Class之间可以用2个空行
  • Class内的方法之间用1个空行

源文件字符编码尽量使用 utf-8

  • ASCII 和 utf-8 的源文件不需要“编码声明”
  • 标准库更严格:符号必须用ASCII,其他尽量英文字母
  • 注释or字符串可以用非ASCII字符 \x \u \U \N
: str1='\xE4'
: print(str1)
ä

: str2 = '\u4f60\u597d'
: print(str2)
你好

: str3='你好'.encode('unicode-escape')
: print(str3)
b'\\u4f60\\u597d'

import 每行导入1个模块

  • import 要有顺序:标准库进口,第三方库,本地库 —— 各组的导入之间要有空行
  • 禁止使用通配符导入 from xxx import *

Module级dunder必须在__future__之后,其他import之前

  • dunder (double underscore, 双下划线)
  • 最早python对__all__这种符号称为魔法变量、魔法函数,后来为了破除迷信,称为 double underscore method,但太长了,被简称为 dunder symbol、dunder method……
  • Module级的 dunder 有2个约束
    • 必须在 from __future__ import ... 后面
    • 必须在其他 import 前面
from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

python 字符串的单引号与双引号等效

空格的讲究有很多

  • 括号(圆括号、方括号)内避免空格
    • 左括号之前避免空格
  • 逗号、冒号、分号之前避免空格,之后可有
    • 索引中的冒号前后都要避免空格
  • 赋值等操作符前后不能因为对齐而添加多个空格
  • 二元运算符两边放置一个空格
    • 优先级高的运算符或操作符的前后不建议有空格
    • 关键字参数和默认值参数的前后不要加空格
# 错误示范:
spam( ham[ 1 ], { eggs: 2 } ) # 括号里面太多空格
ham[1: 9], ham[1 :9], ham[1:9 :3] # 索引冒号前后不应有空格
long_variable = 3
x             = 1  # 不能为了对齐等号而加空格
i=i+1 # =和+左右都要有空格
submitted +=1 # += 右边没有空格
x = x * 2 - 1 # *优先级高,左右不应有空格
def complex(real, imag = 0.0): # 默认参数的=左右不能有空格
    return magic(r = real, i = imag)

function annotations =前后有空格,:->前无后有

python3 中引入了 function annotations,它是一种注释性质的语法,用于声明入参的类型和默认值。

  • 2006年的 PEP3107 首先定义了 Function Annotations
  • 2014年的 PEP484 补充和修订了 Function Annotations
  • 2016年,又发展出了 PEP526,定义了 Variable Annotations
import enforce

@enforce.runtime_validation
def add_int(a: int, b: int = 0)-> int:
    return a + b

print(add_int.__annotations__)
  • 表明入参a是int型,b是int型且默认值0,返回值int型
  • func.__annotations__ 返回函数入参注释信息
  • 有@enforce则强制类型检查,不匹配报错;不加 @enforce 则只做告警,不报错
  • function annotations 的代码风格要求
    • =前后要有空格
    • 冒号和”->”的前无,后有。
def add_int(a:int, b: int=0) -> int: # 错误示范,有3处错误
def add_int(a: int, b: int = 0)-> int: # 正确示范

尾随逗号在Tuple中重要,List中无效;分行中有利,单行中冗余

尾随逗号(Trailing Commas)

FILES0 = ('setup.cfg')  # 0
FILES1 = ('setup.cfg',) # 1
FILES2 = 'setup.cfg',   # 2
FILES3 = ['setup.cfg', 'tox.ini']    # 3
FILES4 = ['setup.cfg', 'tox.ini', ]  # 4
FILES5 = [                           # 5
    'setup.cfg',
    'tox.ini',
    ]
  • 类型:0是str,1/2是tuple,3/4是list
: print(type(FILES0),type(FILES1),type(FILES2),type(FILES3),type(FILES4))
<class 'str'> <class 'tuple'> <class 'tuple'> <class 'list'> <class 'list'>
  • 1==2 —— Tuple中尾随逗号是重要的
In [42]: FILES1==FILES2
Out[42]: True
  • 3==4==5 —— List中尾随逗号是无效的
In [43]: FILES3==FILES4==FILES5
Out[43]: True
  • 5 中每行增加尾随逗号是有意义的,可以减少新增行时的工作量
  • 4 中单行增加尾随逗号是没有意义的。

注释书写规范参考 PEP257

  • 与代码自相矛盾的注释比没注释更差。修改代码时要优先更新注释!
  • 注释段落中的每个句子应该以句号+2个空格结束。
  • 文档化注释(或称为:可生成文档的注释)需遵守 PEP257
    • 将来有空可以单讲一篇,先挖个坑吧

命名:可用一张表格说清

程序猿常见的命名风格多样:

Naming Styles 举例 使用场景 备注
单个小写字母 b 变量名 避免使用小写l(易与1,I混)
单个大写字母 B 变量名 避免使用大写O(易与0混)、I
小写串 lowercase 变量名,包名,模块名 模块名对应文件名,有线系统文件名不区分大小写
带下划线的小写 lower_case_with_underscores 函数名,类的公开方法  
大写串 UPPERCASE 常量 通常在模块级别定义
带下划线的大写串 UPPER_CASE_WITH_UNDERSCORES 常量 同上
驼峰命名法
首字母大写的单词串
CapitalizedWords(CapWords) Class名 专有名次缩写可全大写,如HTTP
匈牙利命名法
混合大小写,首单词小写
mixedCase   python抵制
极少特殊情况下用于函数名
带下划线,首字母大写 Capitalized_Words_With_Underscores   python抵制,丑陋
短前缀分组+匈牙利或驼峰 bsp_getPower   python抵制,与C划清界限
单前置下划线 _single_leading_underscore 用于不想被外部使用的全局变量
或局部变量
类的非公开方法和实例变量
弱内部使用
weak “internal use”
单后置下划线 single_trailing_underscore_ def func(class_='name') 用于避免与 Python关键词的冲突
双前置下划线 __double_leading_underscore 类Foo中的__bar会变成_Foo__bar 当用于命名类属性,会触发名字重整
双前后下划线 __dunder_func__ Dunder(魔法)函数 不要自己发明这样的名字
  • Python中没有属性是真正私有的,所以没有 private 方法。
  • 简单的公开数据属性,最好只公开属性名,没有复杂的访问/修改方法,python的Property提供了很好的封装方法。
  • 为了更好地支持内省,模块要在__all__属性列出公共API。
  • 如果命名空间(包、模块或类)是内部的,里面的接口也是内部的。

编程建议

下面就不单纯是格式(format)的问题了,而是有利于程序运行的内容。

  • 要明白自己在用哪个python解释器
    • 官方整理出来好几十个
      • CPython: https://www.python.org/downloads/ 官网下载的既是
      • CPython衍生的 stacklessPython, wpython, microPython…
      • PyPy:Python in Python
      • Jython:Python in Java
      • IronPython:Python in C#
      • ActivePython
      • WinPython
    • CPython及其衍生底层是用C实现的,所以对C/Python API都有完美支持。非C语言实现的编译器虽然某些操作会快一些,但最大的问题是不支持某些只有C接口的库,如:numpy、scipy……
    • 不同的编译器之间效率有差别,官方建议你仔细研究、斟酌
  • if foo is not None 优于 if not foo is None
  • if foo == None 用法错误
  • 比较排序操作最好实现完整6个操作:__eq__, __ne__, __lt__, __le__, __gt__, __ge__
  • 变量赋值不要用lambda,而要用def,如:f = lambda x: 2*x #不推荐
  • 空”except:”子句(相当于except Exception)会捕捉SystemExit和KeyboardInterrupt异常,难以用Control-C中断程序,并可掩盖其他问题。只适用于2种情况
    • 打印出或记录了traceback,至少让用户将知道已发生错误
    • 代码需要做一些清理工作,并用 raise转发了异常,try…finally可以捕捉到它
  • 本地资源用with,使用with的资源要有独立的enter、exit方法
  • 函数或者方法在没有返回时要明确返回None
def foo(x):
    if x < 0:
        return  # No!请 return None
    return math.sqrt(x)
  • 使用字符串方法而不是string模块
    • Python不支持单字符类型,单字符在 Python 中也是作为一个字符串
# 字符串方法
>>> type(str)   # str是python关键字
<class 'type'>
>>> STR="Hello World"
>>> type(STR)
<class 'str'>
>>> str.lower(STR)
'hello world'
>>> STR.lower() # str.xxx() == STR.xxx()
'hello world'
>>> STR[::-1]   # 翻转字符串
'dlroW olleH'

>>> import string
>>> STR = "Hello {0}"
>>> f = string.Formatter()
>>> f.format(STR,"world") 
'Hello world'
>>> STR.format("World")
'Hello World
  • 字符串方法中检查前后缀:使用 .startswith()和.endswith()代替字符串切片
>>> STR[:5]=="Hello"        # 切片 —— No!
True
>>> STR.startswith("Hello") # Yes!
True
  • 使用isinstance()代替对象类型的比较
if isinstance(obj, int):    # Yes!
if type(obj) is type(1):    # No!
  • 判断序列(字符串、list、tuple)为空:
if not seq:     # Yes!
if len(seq):    # No!
  • 不要用 == 进行布尔比较
if greeting:            # Yes!
if greeting == True     # No!
if greeting is True:    # Worse

自动代码美化工具

要实现 PEP8 中的大部分条目并不难,但要持之以恒、实现所有条目,应该还是挺难的。

一个人要实现 PEP8 中的条目并不难,但要保证一个团队都做到,应该还是挺难的。

如果有一个自动代码美化工具,当我 Ctrl-S 保存文件的时候实时帮我按 PEP8 美化一下,上面2个问题就都不是问题了。

帮我们实现这个愿望有:

  • Google 出品的 YAPF —— 可以满足你这方面的几乎所有需求。
  • Python 出品的 Black —— 左手Python,右手Black,一家人做的东西靠得住。

YAPF

$ pip install yapf
$ yapf [options] [files [files ...]]

常用的 options 有:

  • --diff: 输出diff文件,不修改源文件
  • --style STYLE:
    • “pep8” or “google”
    • 如果当前目录下有 .style.yapf 或 setup.cfg 文件,则从此文件中读取配置项
      • 配置项有几十个,各人慢慢研究

.style.yapf 文件示例:

[yapf]
based_on_style = pep8
spaces_before_comment = 4
split_before_logical_operator = true

Black

用了 Black,相当于放弃了自己的编码风格,完全由 Black 来接手,一个团队的代码就像一个人写的 —— 至少样子上是。

经过一段时间之后,个人手写水平也能提高,逐步逼近 PEP8 的要求。

$ pip install black
$ black [options] [mypythonfile 或 dir]

常用的 options 有:

  • -t [py27|py33|py34|py35|py36|py37|py38]
    • Black 会自动逐个试探每个文件的python版本,应用不同的风格
  • --check:只检查,不修改源文件
  • --diff: 输出diff文件,不修改源文件
  • --include XXX, --exclude XXX: 操作目录时的筛选器

比较

Item YAPF Black 点评
License Apache2.0 MIT 两者都允许修改后闭源,即允许只发布二进制版本,但必须附带License。
Apache要求每个文件头上都要附带,MIT只需要根目录下放一份即可。
Author Google Python 所以Black只支持PEP8,YAPF则可定制
创建日期 2015-03-18 2018-03-14 3月是个好季节
提交次数 1000+ 500+ 截至2019.5
Github Star 9k+ 10k+ 截至2019.5

找一段代码实战比较一下,从左至右依次是:原始文件、black处理后的文件、yapf依pep8处理过的文件

  • 第1行:每次只import1个model —— 没有帮我改过来
  • 第2行:dunder 变量放置在 import 之前 —— 没有帮我改过来
  • 第9-18行:空格、空行,black和yapf都控制的不错
    • 第17行:google把索引中的冒号两边去掉了空格
  • 第19-23行:因为没有超过80列,续行都合并到1行了
  • 第24-25行:PEP8中推荐的写法,black、yapf都做了修正
  • 第28-32行:PEP8中推荐的写法,black微调,yapf大修 —— black稍优
  • 第34行:函数注释都能控制冒号左右的空格
  • 第37行:要用is而不是==来比较None —— 都没修改过来

可以看出 black 和 yapf 能做的和 PEP8 比较还是有限,且目前都还在书写美化上下功夫,在代码优化上不足。

合入IDE

三层境界:

  1. 代码美化频率较低或非强制的客官,实现功能,再绑定个快捷键,就够用了。
  2. 成为粉丝后,想要强制执行美化,对自己毫不留情的客官,就需要捆绑到“文件保存(Ctrl-S)”时自动执行。
  3. 强迫症患者或脑残粉,需要边写边美化的客官,就有上一些监控手段,各大IDE也都为你准备好了Watcher伺候。

VSCode

VSCode 目前是我的主力 Editor,合入 VSCode 是刚需。

为 black 命令创建一个 VSCode 的 Task(下图中我命名为: beautifu my python code),F1–Run Task 的时候选中即可,如下图:

再绑定个快捷键就,爽快了:

VSCode 可以将 Task 配置成 watching task,或使用 “run on save” 之类的 VSCode扩展,可以实现“保存文件”或“其他动作”时自动执行美化 task。

PyCharm

PyCharm 的Plugin中可以搜到一个 black-pycharm 的插件,我试用了一下没玩转,还是自己配置吧。

首先在 Settings 中添加 External Tool:

然后绑定到一个快捷键上:

第1层境界练成!开始第2层:使用 pycharm 的一个 plugin: File Watcher 来实现,先添加插件,然后在Tools中添加一个Watcher,如下图:

使用效果:

Watcher 的配置中有一个Auto-Edit的选项请不要勾选,否则你将会进入第3境界,这种感觉我试了一下,非常酸爽:

Good luck!