Google的Python代码格式化工具:YAPF

既然我们有了这棵树,我们将这棵树上以最小代价找到的路径决策为最佳格式化形式。

目录

介绍

安装

Python版本

用法

格式化风格

示例

模块示例

Knobs

潜在的常见问题

详细细节

介绍

现在Python的大多数格式化工具都是用来去除代码中的lint错误,比如:autopep8,pep8ify等等。但这些工具都显然有一些局限性,比如:遵循 PEP 8 指导的代码可能就不会被格式化。经过格式化的代码并不意味着看起来就更舒服。

相对而言YAPF 采用了一种不同的解决方案,它基于由 Daniel Jasper 开发的 clang-format。大体上来说,这个算法获取代码之后,会把初始代码进行重新编排使得代码尽可能符合风格规范,即便初始代码并没有违背这一规范。这个理念和 Go 语言中的 gofmt 工具相似,就是终结关于格式的各种“圣战”。如果一旦对代码进行修改,就对项目的代码库进行 YAPF 优化,那么整个项目的代码风格都保持统一的话,每次代码审查时,也就没有必要争论风格问题了。

YAPF 的终极目标是,实现由YAPF生成的code能够和遵循编程规范的程序员所编写的code一样优质,这样就可以减轻一些代码维护的工作负担。

安装.

私信小编01 02 03 04 05 即可获取数十套PDF哦!

从pypi社区安装YAPF库:

(可选项)如果你使用的是Python2.7并且需要实现多进程,那么还需要安装futures库如下:

在我看来YAPF目前还处于alpha测试阶段,因而版本迭代可能会非常频繁,所以持续获取最新版本的最佳途径就是clone这个仓库。

注意:如果你打算将YAPF作为一个命令行工具而不是一个库的话,那就没有必要安装YAPF库了,因为YAPF可以由Python解释器来将其以目录形式运行。你已经将YAPF下载/解压到DIR中,那么可以通过如下方式来运行:

Python版本

YAPF支持Python2.7以及3.4.1+

YAPF要求它所要格式化的代码,必须是可以运行该版本YAPF的Python的代码,因此,请在Python3的环境下运行YAPF来格式化Python3的代码(Python2同样如此)

用法

选项:

位置参数:files

可选参数如下

格式化风格

YAPF代码格式化风格是可以配置的,有许多knobs可以用来调整YAPF的格式化方式,详细信息请查阅 style.py模块。

为了控制代码格式化风格,运行YAPF的时候请带上--style参数,这个参数可以接收一个YAPF定义好的格式化风格,比如:pep8,google等等,也可以接收一个指向自定义配置文件的路径。自定义配置文件是一个以[style]开头,内容形如key = value键值对的简单列表(其中键名不区分大小写),示例:

其中based_on_style参数设置决定了自定义风格是基于哪个YAPF预定义风格的。(这样创建自定义的方法就跟创建子类一样)

--style的第三种命令行参数传递形式就是使用字典,示例:

该参数表示自定义风格基于 chromium风格并且将缩进修改为4个空格

YAPF查找formatting style依据的先后顺序如下:

1.依据命令行指定的style

2.依据当前目录或者上级目录下的一个.style.yapf文件中的[style]部分

3.依据当前目录或者上级目录下的set.cfg文件中的[yapf]部分

4.依据home目录下的~/.config/yapf/style文件

如果这些文件都没有找到,YAPF就会使用PEP8作为默认的formatting style

示例

下面是一个可以用YAPF来对代码进行重新格式化的例子,格式重定之前代码如下:

经过YAPF格式重定之后的效果如下:

模块示例

调用yapf的两个主要API是FormatCode 和 FormatFile,这里介绍几个常用参数

参数style_config:一个style名称或者是一个指向包含格式化style设置的配置文件的路径。如果指定为None,就使用设置于style.DEFAULT_STYLE_FACTORY的默认style

参数lines:是一个由一个或多个元组构成的列表,元组有两个整型成员START和END表示格式化的范围。常被用于第三方工具的代码中,用于重新格式化一小块代码而不是整个文件的时候。

参数 print_diff:是布尔类型,返回格式化结果与需要格式化的文本的不同,而不是返回格式化的结果

注意:参数filename是指定插入到diff中的内容,默认是。

参数in-place:将重新格式化的代码写回文件中保存

Knobs

ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT 右括号在一个缩进的位置对齐

ALLOW_MULTILINE_LAMBDAS 允许lambda表达式分多行书写

ALLOW_MULTILINE_DICTIONARY_KEYS 允许字典键值对分多行书写

示例:

BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF 如果一个def或者class嵌套在另一个def或class中,那么就在前者之前插入一个空白行。

示例:

BLANK_LINE_BEFORE_CLASS_DOCSTRING 在一个class-level docstring之前插入一行空白

COALESCE_BRACKETS 对连续括号不进行拆分,仅当设置了DEDENT_CLOSING_BRACKETS的时候才有用

示例:

重新格式化之后如下所示:

COLUMN_LIMIT 列数限制(或者称为最大行长)

CONTINUATION_INDENT_WIDTH 连续行的缩进宽度

DEDENT_CLOSING_BRACKETS 如果括号内的表达式不能在一行内表示完,那么就将右括号独立成行,并取消缩进。适用于所有类型的括号,包括函数的定义和调用

示例:

EACH_DICT_ENTRY_ON_SEPARATE_LINE 将字典的每一个条目都放在本行上。

I18N_COMMENT 有国际统一注释的正则表达式。这一注释的存在阻止了对着一整行的重新格式化,因为注释不要求紧随在需要翻译的字符串后面

I18N_FUNCTION_CALL 对于国际统一的函数调用名,这个函数的存在阻止了对这一整行进行重新格式化,因为这个函数的字符串不能与i18n本地注释分离

INDENT_DICTIONARY_VALUE 如果字典键值对不能将字典的值写在键的同一行,就将值换行缩进。

示例:

INDENT_WIDTH 缩进列数

JOIN_MULTIPLE_LINES 将多个短行拼接成一行。

例如:single line if statements

SPACES_AROUND_POWER_OPERATOR 设置为True表示在**前后使用空格

SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN 设置为True表示在赋值运算符前后使用空格

SPACES_BEFORE_COMMENT 在后续注释前面需要空格的数量

SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET 在结束的逗号和列表的右括号之间插入一个空格等等

SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED 如果参数列表以逗号结束,那么就在参数前面进行拆分

SPLIT_BEFORE_BITWISE_OPERATOR 设置为True,表示在&, | 和 ^之前拆分比在其之后拆分要好。

SPLIT_BEFORE_DICT_SET_GENERATOR 在字典或者集合生成器之前进行拆分,注意for之前的拆分

示例:

SPLIT_BEFORE_FIRST_ARGUMENT 如果一个参数列表要进行拆分,那就在第一个参数之前进行拆分。

SPLIT_BEFORE_LOGICAL_OPERATOR 设置为True表示在and和or之前进行拆分,而False表示在其后进行拆分

SPLIT_BEFORE_NAMED_ASSIGNS 将指定内容拆分为独立的一行

SPLIT_PENALTY_AFTER_OPENING_BRACKET 左括号的右侧进行拆分的坏处

SPLIT_PENALTY_AFTER_UNARY_OPERATOR 在一元操作符后面分行的坏处

SPLIT_PENALTY_BEFORE_IF_EXPR 在if表达式之前进行拆分的坏处

SPLIT_PENALTY_BITWISE_OPERATOR 在&, |, 和 ^前后进行拆分的坏处

SPLIT_PENALTY_EXCESS_CHARACTER 字符超过列限制的坏处

SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT 将展开的行进行拆分是不好的,行拆分越多越不好。

SPLIT_PENALTY_IMPORT_NAMES 将一个列表的import名称分成多行是不好的

示例:

经过格式化以后如下所示:

SPLIT_PENALTY_LOGICAL_OPERATOR 在and和or运算符前后分行是不好的

潜在的常见问题

1.为什么YAPF破坏了我很棒的格式?

YAPF只是尽全力使得格式正确,但是对于一些代码,它可能没有人工格式化的代码规范,特别是对大量的代码而言,经过YAPF重新格式化会使得代码格式变得更糟糕。引起这样结果的原因是多样的,但是从本质上来说,YAPF只是一个帮助开发的简单工具,它会将代码格式化成符合格式规范的代码,但这并不一定具有可读性。为减少这种情况的发生,你需要指定YAPF重新格式化时需要忽视的代码部分:

你也可以对格式单一的代码段禁用格式化,例如:

为了保留正确缩进的右括号,请在style选项中使用dedent_closing_brackets。在这种情况下,所有括号内的代码,包括函数的定义和调用都会使用这种风格。这样就会产生一致性的格式化代码。

2.为什么不改善现有的工具?

现有的工具要考虑到不同的目标,并需要大量的修改转换以便使用clang-format算法。我们希望使用clang-format重组算法。

3.我可以在程序中使用YAPF吗?

答案是肯定的!YAPF被设计用于作为一个库以及一个命令行工具来使用。这意味着使用YAPF时,一个工具或IDE插件是免费的。

详细细节

算法设计:YAPF主要的数据结构是 UnwrappedLine对象。它是一个由FormatTokens构成的列表,因此在没有限制列数的情况下我们会将其写在一行上。例外的是,当一条语句中间有注释时,这时会迫使这一行被格式化成多行,格式化工具一次只能针对一个UnwrappedLine对象做出处理。一个UnwrappedLine通常不会影响前一行或者后一行的格式化。算法会将两行以上的UnwrappedLines合并成一行。

比如当一个if-then语句非常短小,可以写在一行上的时候,比如:

YAPF的格式化算法会创建一棵加权树作为这个算法的解空间

树中的每一个节点都表示一个格式化的决策结果——比如:是否在标记之前进行分行等

每一个格式化决策都会有与之相关的权重

因此,在两个节点之间的边上就可以获得权重。

(事实上,加权数并没有单独的边对象,因此权重驻留在节点当中)

比如,以下述代码段为例,假设第一行的代码超出了列数限制,需要进行格式化处理。

对于第一行,算法会创建一棵树,树上的每一个节点(也就是一个FormattingDecisionState对象)都表示在这个标记处这一行的状态,由这棵树决策是否在标记的前面分行。

注意:

FormatDecisionState对象拷贝的是值,因此树上的每一个节点都是唯一的,并且修改一个节点并不会影响其他节点

启发式算法被用来确定拆分与不拆分各自产生的代价,由于一个节点会将树的状态保持直到有一个标记插入,所以它可以很容易地判断一个拆分决定是否会违反样式要求。比如,当不对前一个标记和新加入的标记进行拆分的话,启发式算法能够将一个额外的破坏作用到边上。

有些情况下我们并不希望将一行进行拆开,因为这样做会损坏代码(比如,这时候会需要反斜线,而反斜线又让人很不满意)。对第一行而言,头三个标记字符def xxxxxxxxxx和 ( 并不需要拆分。而对于 ) 和 : 之间的部分也不希望被拆分,这些区域被视作为不可分割的。在不可分割的区域内,通过将其作为左子树,来将不拆分这一决策反应在加权树上

既然我们有了这棵树,我们将这棵树上以最小代价找到的路径决策为最佳格式化形式。

________________________________________

YAPF不是一个Google的官方产品,只是代码是归Google拥有。

  • 发表于:
  • 原文链接:https://kuaibao.qq.com/s/20181025A1NE0300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券