Black 代码风格¶
代码风格¶
Black 旨在保持一致性、通用性、可读性和减少 Git 差异。类似的语言结构使用类似的规则进行格式化。风格配置选项有意限制,并且很少添加。尽可能少地考虑之前的格式,只有像神奇的尾随逗号这样的罕见例外。Black 使用的编码风格可以看作是 PEP 8 的严格子集。
本文档描述了当前的格式化风格。如果您有兴趣尝试一下风格的走向,请查看未来风格并尝试运行black --preview
。
Black 如何换行¶
Black 忽略之前的格式,并对您的代码应用统一的水平和垂直空白。水平空白的规则可以概括为:无论什么让pycodestyle
开心。
至于垂直空白,Black 尝试在每行渲染一个完整的表达式或简单语句。如果这适合分配的线长,很好。
# in:
j = [1,
2,
3
]
# out:
j = [1, 2, 3]
如果没有,Black 将查看第一对匹配括号的内容,并将其放在单独的缩进行。
# in:
ImportantClass.important_method(exc, limit, lookup_lines, capture_locals, extra_argument)
# out:
ImportantClass.important_method(
exc, limit, lookup_lines, capture_locals, extra_argument
)
如果仍然不符合要求,它将使用相同的规则进一步分解内部表达式,每次缩进匹配括号。如果匹配括号对的内容是逗号分隔的(例如参数列表或字典字面量等等),那么 Black 将首先尝试将它们与匹配括号放在同一行。如果这样做不可行,它将把它们放在单独的行。
# in:
def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, 'w') as f:
...
# out:
def very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = True,
debug: bool = False,
):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
如果数据结构字面量(元组、列表、集合、字典)或一行 "from" 导入不能容纳在分配的长度内,它将始终拆分为每行一个元素。这最大程度地减少了差异,并使代码的读者能够找到引入特定条目的提交。这也使 Black 与使用预制black
配置文件或手动配置的isort兼容。
您可能已经注意到,结束括号总是缩进,并且始终添加尾随逗号。这种格式会产生更小的差异;当您添加或删除元素时,它始终只是一行。此外,将结束括号缩进提供了两个不同代码部分之间的清晰分隔符,否则它们将共享相同的缩进级别(例如上面示例中的参数列表和文档字符串)。
Black 优先使用圆括号而不是反斜杠,并且会删除反斜杠(如果找到)。
# in:
if some_short_rule1 \
and some_short_rule2:
...
# out:
if some_short_rule1 and some_short_rule2:
...
# in:
if some_long_rule1 \
and some_long_rule2:
...
# out:
if (
some_long_rule1
and some_long_rule2
):
...
反斜杠和多行字符串是 Python 语法中打破显着缩进的两个地方之一。您永远不需要使用反斜杠,它们用于强制语法接受原本会导致解析错误的断行。这使得它们看起来很混乱,也很难修改。这就是为什么 Black 总是摆脱它们的原因。
如果您正在使用反斜杠,这是一个明确的信号,表明如果您稍微重构一下代码,可以做得更好。我希望上面的一些示例表明您有很多方法可以做到这一点。
行长¶
您可能已经注意到奇特的默认行长。Black 默认每行 88 个字符,正好超过 80 的 10%。发现这个数字产生的文件明显比坚持使用 80(最流行的)甚至 79(标准库使用)要短。总的来说,90 左右似乎是明智的选择。
如果您是按代码行数付费的,可以在命令行中使用--line-length
传递更小的数字。Black 将尝试尊重这一点。但是,有时它无法做到这一点,除非违反其他规则。在这些罕见的情况下,自动格式化的代码将超过您分配的限制。
您也可以增加它,但请记住,视力障碍人士发现使用超过 100 个字符的行长更难。它还会对典型屏幕分辨率下的并排差异审查产生负面影响。长行还会使代码在文档或幻灯片中难以整齐地呈现。
Flake8 和其他 linter¶
有关 linter 兼容性,请参阅将 Black 与其他工具一起使用。
空行¶
Black 避免使用多余的垂直空白。这符合 PEP 8 的精神,PEP 8 指出函数内部的垂直空白应该只谨慎使用。
Black 允许在函数内部使用单行空行,以及模块级别上由原始编辑器留下的单行和双行空行,除非它们在括号表达式内。由于此类表达式始终被重新格式化为适合最小空间,因此此空白将丢失。
# in:
def function(
some_argument: int,
other_argument: int = 5,
) -> EmptyLineInParenWillBeDeleted:
print("One empty line above me will be kept!")
def this_is_okay_too():
print("No empty line here")
# out:
def function(
some_argument: int,
other_argument: int = 5,
) -> EmptyLineInParenWillBeDeleted:
print("One empty line above me will be kept!")
def this_is_okay_too():
print("No empty line here")
它还将在函数定义之前和之后插入适当的间距。函数定义之前和之后是单行,模块级别函数和类之前和之后是双行。Black 不会在函数/类定义和紧接在给定函数/类之前的独立注释之间放置空行。
Black 将在类级别文档字符串和第一个后续字段或方法之间强制使用单行空行。这符合PEP 257。
Black 不会在函数文档字符串之后插入空行,除非由于紧随其后的内部函数而需要该空行。
尾随逗号¶
Black 将在逗号分割的表达式中添加尾随逗号,其中每个元素都在它自己的行上。这包括函数签名。
在添加尾随逗号的一个例外是包含*
、*args
或**kwargs
的函数签名。在这种情况下,尾随逗号仅在 Python 3.6 中使用是安全的。Black 将检测您的文件是否已经是 3.6+ 仅版本,并在这种情况下使用尾随逗号。如果您想知道它是如何知道的,它会查找 f 字符串和在包含星号的函数签名中现有尾随逗号的使用情况。换句话说,如果您想要在这种情况下使用尾随逗号,而 Black 没有识别出它是安全的,请手动将其放在那里,Black 将保留它。
现有的尾随逗号通知 Black 始终将当前括号对的内容展开为每行一个项目。有关此主题的更多信息,请参阅下面的务实性部分。
字符串¶
Black 优先使用双引号("
和 """
)而不是单引号('
和 '''
)。只要不会导致比之前更多的反斜杠转义,它就会用前者替换后者。
Black 还标准化字符串前缀。前缀字符将转换为小写,但大写“R”前缀除外,Unicode 字面量标记(u
)将被删除,因为它们在 Python 3 中毫无意义,并且在多个字符的情况下,"r" 将放在首位,就像口语一样:"raw f-string"。
标准化为单一形式引号的主要原因是美观。在所有地方使用一种引号可以减少读者的分心。它还将使 Black 的未来版本能够合并最终出现在同一行的连续字符串字面量(有关详细信息,请参阅#26)。
为什么选择双引号?它们预见英语文本中的撇号。它们与PEP 257中描述的文档字符串标准相匹配。双引号中的空字符串(""
)不可能与一个双引号混淆,无论使用什么字体和语法高亮显示。最重要的是,字符串的双引号与 Python 大量交互的 C 保持一致。
在某些键盘布局(如美式英语)上,键入单引号比键入双引号更容易。后者需要使用 Shift 键。我的建议是继续使用任何键入速度更快的键,让 Black 处理转换。
如果您要在具有现有字符串约定的大型项目中采用 Black(例如流行的“单引号用于数据,双引号用于人类可读字符串”),您可以在命令行中传递--skip-string-normalization
。这旨在作为采用助手,避免在新项目中使用此功能。
Black 还会处理文档字符串。首先,无论是引号还是里面的文本,文档字符串的缩进都会被校正,但文本中的相对缩进会保留。每个行尾的多余尾部空格以及文档字符串末尾不必要的换行符都会被删除。所有开头的制表符都将转换为空格,但文本内部的制表符将保留。一行文档字符串前后的空格将被删除。
数字字面量¶
Black 将大多数数字字面量标准化为对语法部分使用小写字母,对数字本身使用大写字母:0xAB
而不是 0XAB
,以及 1e10
而不是 1E10
。
换行符和二元运算符¶
Black 会在将代码块拆分到多行时,在二元运算符之前换行。这样做是为了使 Black 符合 PEP 8 样式指南中的最新更改,该指南强调这种方法可以提高可读性。
几乎所有运算符都会被单空格包围,唯一的例外是单目运算符(+
、-
和 ~
)以及当两个操作数都比较简单时的幂运算符。对于幂运算,如果操作数只是 NAME、数字 CONSTANT 或属性访问(允许链式属性访问),无论是否有前面的单目运算符,都将其视为简单操作数。
# For example, these won't be surrounded by whitespace
a = x**y
b = config.base**5.2
c = config.base**runtime.config.exponent
d = 2**5
e = 2**~5
# ... but these will be surrounded by whitespace
f = 2 ** get_exponent()
g = get_x() ** get_y()
h = config['base'] ** 2
切片¶
PEP 8 建议 将切片中的 :
视为优先级最低的二元运算符,并在两侧留出相同的空格,除非省略了一个参数(例如 ham[1 + 1 :]
)。对于“简单表达式”(ham[lower:upper]
),它建议在 :
运算符周围不留空格,而对于“复杂表达式”(ham[lower : upper + offset]
),则建议留出额外空格。Black 将除变量名称以外的任何内容都视为“复杂”(ham[lower : upper + 1]
)。它还指出,对于扩展切片,两个 :
运算符必须具有相同的空格量,除非省略了一个参数(ham[1 + 1 ::]
)。Black 会一致地执行这些规则。
这种行为可能会在像 Flake8 这样的样式指南强制执行工具中引发 E203 whitespace before ':'
警告。由于 E203
不符合 PEP 8,您应该告诉 Flake8 忽略这些警告。
括号¶
在 Python 语法中,一些括号是可选的。任何表达式都可以用一对括号括起来,形成一个原子。有一些有趣的例子
if (...):
while (...):
for (...) in (...):
assert (...), (...)
from X import (...)
像这样的赋值
target = (...)
target: type = (...)
some, *un, packing = (...)
augmented += (...)
在这些情况下,如果整个语句在一行中就能放得下,或者内部表达式没有任何分隔符需要进一步拆分,则会移除括号。如果只有一个分隔符,并且表达式以方括号开头或结尾,则也可以成功省略括号,因为现有的方括号对无论如何都会将表达式整齐地组织起来。否则,括号将被添加。
请注意,Black 不会添加或删除任何额外的嵌套括号,这些括号可能是为了清晰度或进一步的代码组织而添加的。例如,这些括号不会被删除
return not (this or that)
decision = (maybe.this() and values > 0) or (maybe.that() and values < 0)
调用链¶
一些流行的 API,比如 ORM,使用调用链。这种 API 风格被称为 流畅接口。Black 通过将调用或索引操作之后的点视为优先级非常低的定界符来格式化这些内容。将行为展示出来比解释更容易。看一下这个例子
def example(session):
result = (
session.query(models.Customer.id)
.filter(
models.Customer.account_id == account_id,
models.Customer.email == email_address,
)
.order_by(models.Customer.id.asc())
.all()
)
类型存根文件¶
PEP 484 描述了 Python 中类型提示的语法。类型的一种用例是为无法直接包含类型的模块提供类型注释(它们可能用 C 语言编写,或者它们可能是第三方,或者它们的实现可能过于动态,等等)。
为了解决这个问题,可以使用 具有 .pyi
文件扩展名的存根文件 来描述外部模块的类型信息。这些存根文件省略了它们所描述的类和函数的实现,而是只包含文件的结构(列出全局变量、函数和类及其成员)。对于这些文件,推荐的代码风格比 PEP 8 更简洁
在与类/函数签名相同的行上,首选
...
;避免在连续的模块级函数、名称或方法之间使用垂直空格,以及在一个类中使用字段之间的垂直空格;
在顶层类定义之间使用一个空行,或者如果类非常小,则不使用空行。
Black 会强制执行上述规则。还有其他一些格式化 .pyi
文件的指南,这些指南还没有强制执行,但在格式化程序的未来版本中可能会强制执行
首选
...
而不是pass
;避免在类型注释中使用字符串字面量,存根文件本机支持前向引用(例如 Python 3.7 代码,使用
from __future__ import annotations
);即使对于面向旧版 Python 的存根,也使用变量注释而不是类型注释。
行尾¶
Black 会根据文件的第一个行尾来规范化行尾(\n
或 \r\n
)。
换页符¶
Black 会保留在模块级其他为空的行上的换页符。对于一组连续的空行,只保留一个换页符。如果连续有两行空行,则将换页符放在第二行。
实用主义¶
早期的 Black 版本在某些方面曾经是绝对主义者。他们效仿其最初的作者。这在当时很好,因为它使实现更简单,而且还没有很多用户。没有报告很多边缘情况。作为一个成熟的工具,Black 对它通常坚持的规则做了一些例外。本节记录了这些例外是什么以及这样做的原因。
神奇的尾部逗号¶
一般来说,Black 不会考虑现有的格式。
但是,在某些情况下,您在代码中放置了一个简短的集合或函数调用,但您预计它将来会增长。
例如
TRANSLATIONS = {
"en_us": "English (US)",
"pl_pl": "polski",
}
早期的 Black 版本曾经无情地将它们压缩成一行(它可以放得下!)。现在,您可以通过在集合中自己添加尾部逗号来表示您不想要这样。当您这样做时,Black 会知道始终将您的集合拆分成每行一个项目。
如何让它停止?只需删除尾部逗号,如果 Black 可以放得下,它会将您的集合压缩成一行。
如果必须,您可以使用选项 --skip-magic-trailing-comma
/ -C
来恢复早期 Black 版本的行为。
r”strings” 和 R”strings”¶
Black 会规范化字符串引号以及字符串前缀,将它们都改为小写。此规则的一个例外是 r-strings。事实证明,非常流行的 MagicPython 语法高亮程序(默认情况下由 GitHub 和 Visual Studio Code 等使用)区分 r-strings 和 R-strings。前者被语法高亮显示为正则表达式,而后者被视为真正的原始字符串,没有特殊的语义。
格式化前后的 AST¶
当使用 --safe
(默认)运行时,Black 会检查格式化前后的代码在语义上是否等效。此检查是通过比较源代码的 AST 与目标代码的 AST 来完成的。在 AST 不同的三种有限情况下
Black 会清理文档字符串的前导和尾部空格,并在需要时重新缩进它们。这是用户报告的最受欢迎的格式化程序功能之一,用于修复文档字符串中的空格问题。虽然结果在技术上是 AST 差异,但由于形成文档字符串的各种可能性,我们所知的文档字符串的所有实际使用情况无论如何都会清理缩进和前导/尾部空格。
Black 管理一些语句的可选括号。对于
del
语句,是否包含包装括号会改变生成的 AST,但在解释器中语义上是等效的。Black 可能会移动注释,包括类型注释。从 Python 3.8 开始,这些是 AST 的一部分。虽然该工具为这些注释实现了一些特殊情况,但不能保证它们会保留在源代码中的位置。请注意,这不会改变源代码的运行时行为。
为了说明问题,代码等效性检查是 Black 的一项功能,其他格式化程序根本没有实现。对我们来说,确保代码的行为与格式化之前相同至关重要。我们将其视为一项功能,并且将来没有计划放松此功能。上面列出的例外情况源于用户反馈或工具的实现细节。在每种情况下,我们都尽了应尽的努力,以确保 AST 差异不会产生任何实际影响。
注释¶
Black 不会格式化注释内容,但它会在同一行上的代码和注释之间强制使用两个空格,并在注释文本开始之前使用一个空格。它会尊重一些需要特定间距规则的注释类型:shebang(
#! comment
)、文档注释(#: comment
)、带有长串哈希的节注释以及 Spyder 单元格。哈希后的不间断空格也会保留。注释有时可能会由于格式化更改而移动,这会破坏将特殊含义赋予它们的工具。有关更多讨论,请参阅格式化前后 AST。