YAPF 是一个基于clang-format
的 Python 格式化程序(由 Daniel Jasper 开发)。本质上,该算法获取代码并计算符合配置样式的最佳格式。它消除了维护代码的大量苦差事。
最终目标是 YAPF 生成的代码与程序员遵循风格指南编写的代码一样好。
请注意, YAPF 不是 Google 的官方产品(实验性或其他形式),它只是碰巧归 Google 所有的代码。
要从 PyPI 安装 YAPF:
$ pip install yapf
YAPF仍处于“beta”阶段,发布的版本可能会经常变化;因此,了解最新开发的最佳方法是克隆此存储库或直接从 github 安装:
$ pip install git+https://github.com/google/yapf.git
请注意,如果您打算将 YAPF 用作命令行工具而不是库,则无需安装。 YAPF 支持由 Python 解释器作为目录运行。如果您将 YAPF 克隆/解压缩到DIR
中,则可以运行:
$ PYTHONPATH=DIR python DIR/yapf [options] ...
YAPF 通过社区扩展或插件得到多个编辑器的支持。请参阅编辑器支持以获取更多信息。
YAPF 支持 Python 3.7+。
usage: yapf [-h] [-v] [-d | -i | -q] [-r | -l START-END] [-e PATTERN]
[--style STYLE] [--style-help] [--no-local-style] [-p] [-m] [-vv]
[files ...]
Formatter for Python code.
positional arguments:
files reads from stdin when no files are specified.
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-d, --diff print the diff for the fixed source
-i, --in-place make changes to files in place
-q, --quiet output nothing and set return value
-r, --recursive run recursively over directories
-l START-END, --lines START-END
range of lines to reformat, one-based
-e PATTERN, --exclude PATTERN
patterns for files to exclude from formatting
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg or pyproject.toml file
located in the same directory as the source or one of
its parent directories (for stdin, the current
directory is used).
--style-help show style settings and exit; this output can be saved
to .style.yapf to make your settings permanent
--no-local-style don't search for local style definition
-p, --parallel run YAPF in parallel when formatting multiple files.
-m, --print-modified print out file names of modified files
-vv, --verbose print out file names while processing
通常,YAPF 在程序成功终止时返回零,否则返回非零。
如果提供了--diff
,则当不需要更改时 YAPF 返回零,否则返回非零(包括程序错误)。您可以在 CI 工作流程中使用它来测试代码是否已采用 YAPF 格式。
除了排除命令行上提供的模式之外,YAPF 还会查找名为.yapfignore
或pyproject.toml
的文件中指定的其他模式,该文件位于调用 YAPF 的工作目录中。
.yapfignore
的语法类似于 UNIX 的文件名模式匹配:
* matches everything
? matches any single character
[seq] matches any character in seq
[!seq] matches any character not in seq
请注意,任何条目都不应以./
开头。
如果您使用pyproject.toml
,则排除模式由[tool.yapfignore]
部分中的ignore_patterns
键指定。例如:
[tool.yapfignore]
ignore_patterns = [
" temp/**/*.py " ,
" temp2/*.py "
]
YAPF 使用的格式化样式是可配置的,并且有许多“旋钮”可用于调整 YAPF 的格式化方式。有关完整列表,请参阅style.py
模块。
要控制样式,请使用--style
参数运行 YAPF。它接受预定义样式之一(例如, pep8
或google
)、指定所需样式的配置文件的路径或键/值对的字典。
配置文件是带有[style]
标题的(不区分大小写) key = value
对的简单列表。例如:
[style]
based_on_style = pep8
spaces_before_comment = 4
split_before_logical_operator = true
based_on_style
设置确定此自定义样式基于哪种预定义样式(将其视为子类化)。预定义了四种样式:
pep8
(默认)google
(基于 Google Python 风格指南)yapf
(用于 Google 开源项目)facebook
有关详细信息,请参阅style.py
中的_STYLE_NAME_TO_FACTORY
。
也可以在命令行上使用字典执行相同的操作。例如:
--style= ' {based_on_style: pep8, indent_width: 2} '
这将采用pep8
基本样式并将其修改为具有两个空格缩进。
YAPF将通过以下方式搜索格式化样式:
.style.yapf
文件的[style]
部分中。setup.cfg
文件的[yapf]
部分中。pyproject.toml
文件的[tool.yapf]
部分中。~/.config/yapf/style
文件的[style]
部分中。如果未找到这些文件,则使用默认样式 PEP8。
YAPF 可以执行的格式化类型的示例,它将采用以下丑陋的代码:
x = { 'a' : 37 , 'b' : 42 ,
'c' : 927 }
y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}' . format ( 'world' )
class foo ( object ):
def f ( self ):
return 37 * - + 2
def g ( self , x , y = 42 ):
return y
def f ( a ) :
return 37 + - + a [ 42 - x : y ** 3 ]
并将其重新格式化为:
x = { 'a' : 37 , 'b' : 42 , 'c' : 927 }
y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}' . format ( 'world' )
class foo ( object ):
def f ( self ):
return 37 * - + 2
def g ( self , x , y = 42 ):
return y
def f ( a ):
return 37 + - + a [ 42 - x : y ** 3 ]
调用 YAPF 的两个主要 API 是FormatCode
和FormatFile
,它们共享几个参数,如下所述:
> >> from yapf . yapflib . yapf_api import FormatCode # reformat a string of code
> >> formatted_code , changed = FormatCode ( "f ( a = 1, b = 2 )" )
> >> formatted_code
'f(a=1, b=2) n '
> >> changed
True
style_config
参数:样式名称或包含格式化样式设置的文件的路径。如果没有指定,则使用style.DEFAULT_STYLE_FACTORY
中设置的默认样式。
> >> FormatCode ( "def g(): n return True" , style_config = 'pep8' )[ 0 ]
'def g(): n return True n '
lines
参数:我们要格式化的行(整数)元组列表,[开始,结束]。这些行是从 1 开始索引的。当重新格式化代码片段而不是整个文件时,第三方代码(例如,IDE)可以使用它。
> >> FormatCode ( "def g( ): n a=1 n b = 2 n return a==b" , lines = [( 1 , 1 ), ( 2 , 3 )])[ 0 ]
'def g(): n a = 1 n b = 2 n return a==b n '
print_diff
(bool):返回一个将格式化源转换为重新格式化源的 diff,而不是返回重新格式化的源。
>>> print(FormatCode("a==b", filename="foo.py", print_diff=True)[0])
--- foo.py (original)
+++ foo.py (reformatted)
@@ -1 +1 @@
- a==b
+ a == b
注意: FormatCode
的filename
参数是插入到 diff 中的内容,默认为<unknown>
。
FormatFile
从传递的文件中返回重新格式化的代码及其编码:
> >> from yapf . yapflib . yapf_api import FormatFile # reformat a file
> >> print ( open ( "foo.py" ). read ()) # contents of file
a == b
> >> reformatted_code , encoding , changed = FormatFile ( "foo.py" )
> >> formatted_code
'a == b n '
> >> encoding
'utf-8'
> >> changed
True
in_place
参数将重新格式化的代码保存回文件中:
> >> FormatFile ( "foo.py" , in_place = True )[: 2 ]
( None , 'utf-8' )
> >> print ( open ( "foo.py" ). read ()) # contents of file (now fixed)
a == b
选项:
usage: yapf-diff [-h] [-i] [-p NUM] [--regex PATTERN] [--iregex PATTERN][-v]
[--style STYLE] [--binary BINARY]
This script reads input from a unified diff and reformats all the changed
lines. This is useful to reformat all the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 --no-color --relative HEAD^ | yapf-diff -i
svn diff --diff-cmd=diff -x-U0 | yapf-diff -p0 -i
It should be noted that the filename contained in the diff is used
unmodified to determine the source file to update. Users calling this script
directly should be careful to ensure that the path in the diff is correct
relative to the current working directory.
optional arguments:
-h, --help show this help message and exit
-i, --in-place apply edits to files instead of displaying a diff
-p NUM, --prefix NUM strip the smallest prefix containing P slashes
--regex PATTERN custom pattern selecting file paths to reformat
(case sensitive, overrides -iregex)
--iregex PATTERN custom pattern selecting file paths to reformat
(case insensitive, overridden by -regex)
-v, --verbose be more verbose, ineffective without -i
--style STYLE specify formatting style: either a style name (for
example "pep8" or "google"), or the name of a file
with style settings. The default is pep8 unless a
.style.yapf or setup.cfg or pyproject.toml file
located in the same directory as the source or one of
its parent directories (for stdin, the current
directory is used).
--binary BINARY location of binary to use for YAPF
ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT
将右括号与视觉凹痕对齐。
ALLOW_MULTILINE_LAMBDAS
允许在多行上格式化 lambda。
ALLOW_MULTILINE_DICTIONARY_KEYS
允许字典键存在于多行中。例如:
x = {
( 'this is the first element of a tuple' ,
'this is the second element of a tuple' ):
value ,
}
ALLOW_SPLIT_BEFORE_DEFAULT_OR_NAMED_ASSIGNS
允许在参数列表中的默认/命名分配之前进行拆分。
ALLOW_SPLIT_BEFORE_DICT_VALUE
允许在字典值之前进行分割。
ARITHMETIC_PRECEDENCE_INDICATION
让间距指示运算符优先级。例如:
a = 1 * 2 + 3 / 4
b = 1 / 2 - 3 * 4
c = ( 1 + 2 ) * ( 3 - 4 )
d = ( 1 - 2 ) / ( 3 + 4 )
e = 1 * 2 - 3
f = 1 + 2 + 3 + 4
将格式化如下以指示优先级:
a = 1 * 2 + 3 / 4
b = 1 / 2 - 3 * 4
c = ( 1 + 2 ) * ( 3 - 4 )
d = ( 1 - 2 ) / ( 3 + 4 )
e = 1 * 2 - 3
f = 1 + 2 + 3 + 4
BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION
设置顶级函数和类定义周围所需的空行数。例如:
class Foo :
pass
# <------ having two blank lines here
# <------ is the default setting
class Bar :
pass
BLANK_LINE_BEFORE_CLASS_DOCSTRING
在类级别文档字符串之前插入一个空行。
BLANK_LINE_BEFORE_MODULE_DOCSTRING
在模块文档字符串之前插入一个空行。
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF
在直接嵌套在另一个
def
或class
中的def
或class
之前插入一个空行。例如:
class Foo :
# <------ this blank line
def method ():
pass
BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES
设置顶级导入和变量定义之间所需的空行数。对于与 isort 等工具的兼容性很有用。
COALESCE_BRACKETS
不要分割连续的括号。仅当设置了
DEDENT_CLOSING_BRACKETS
或INDENT_CLOSING_BRACKETS
时才相关。例如:
call_func_that_takes_a_dict (
{
'key1' : 'value1' ,
'key2' : 'value2' ,
}
)
将重新格式化为:
call_func_that_takes_a_dict ({
'key1' : 'value1' ,
'key2' : 'value2' ,
})
COLUMN_LIMIT
列限制(或最大行长度)
CONTINUATION_ALIGN_STYLE
连续对齐的样式。可能的值为:
SPACE
:使用空格进行连续对齐。这是默认行为。FIXED
:使用固定数量(CONTINUATION_INDENT_WIDTH
)的列(即CONTINUATION_INDENT_WIDTH
/INDENT_WIDTH
选项卡或CONTINUATION_INDENT_WIDTH
空格)进行连续对齐。VALIGN-RIGHT
:将连续线垂直对齐到多个INDENT_WIDTH
列。如果无法将连续行与缩进字符垂直对齐,则稍微向右(一个制表符或几个空格)。
CONTINUATION_INDENT_WIDTH
用于续行的缩进宽度。
DEDENT_CLOSING_BRACKETS
如果括号内的表达式无法容纳在一行中,请将右括号放在单独的行上,并缩进。适用于各种括号,包括函数定义和调用。例如:
config = {
'key1' : 'value1' ,
'key2' : 'value2' ,
} # <--- this bracket is dedented and on a separate line
time_series = self . remote_client . query_entity_counters (
entity = 'dev3246.region1' ,
key = 'dns.query_latency_tcp' ,
transform = Transformation . AVERAGE ( window = timedelta ( seconds = 60 )),
start_ts = now () - timedelta ( days = 3 ),
end_ts = now (),
) # <--- this bracket is dedented and on a separate line
DISABLE_ENDING_COMMA_HEURISTIC
如果列表以逗号结尾,则禁用启发式将每个列表元素放在单独的行上。
注意:此标志的行为在 v0.40.3 中发生了变化。以前,如果此标志为 true,我们将拆分包含尾随逗号或注释的列表。现在,我们有一个单独的标志
DISABLE_SPLIT_LIST_WITH_COMMENT
,它可以在列表包含注释时控制拆分。要获得旧的行为,请将两个标志设置为 true。更多信息请参见 CHANGELOG.md。
DISABLE_SPLIT_LIST_WITH_COMMENT
不要将每个元素放在包含插页式注释的列表中的新行上。
没有此标志(默认):
[ a, b, # c ]
有了这个标志:
[ a, b, # c ]
这反映了 clang-format 的行为,对于形成列表中元素的“逻辑组”很有用。它也适用于函数声明。
EACH_DICT_ENTRY_ON_SEPARATE_LINE
将每个字典条目放在自己的行上。
FORCE_MULTILINE_DICT
即使该行短于
COLUMN_LIMIT
也要遵守EACH_DICT_ENTRY_ON_SEPARATE_LINE
。
I18N_COMMENT
国际化注释的正则表达式。此注释的存在会停止对该行的重新格式化,因为注释需要位于它们翻译的字符串旁边。
I18N_FUNCTION_CALL
国际化函数调用名称。此函数的存在会停止在该行上重新格式化,因为它所具有的字符串无法从 i18n 注释中移开。
INDENT_BLANK_LINES
设置为
True
更喜欢缩进空行而不是空行
INDENT_CLOSING_BRACKETS
如果括号内的表达式无法容纳在一行中,请将右括号放在单独的行上并缩进。适用于各种括号,包括函数定义和调用。例如:
config = {
'key1' : 'value1' ,
'key2' : 'value2' ,
} # <--- this bracket is indented and on a separate line
time_series = self . remote_client . query_entity_counters (
entity = 'dev3246.region1' ,
key = 'dns.query_latency_tcp' ,
transform = Transformation . AVERAGE ( window = timedelta ( seconds = 60 )),
start_ts = now () - timedelta ( days = 3 ),
end_ts = now (),
) # <--- this bracket is indented and on a separate line
INDENT_DICTIONARY_VALUE
如果字典值无法与字典键放在同一行,则缩进字典值。例如:
config = {
'key1' :
'value1' ,
'key2' : value1 +
value2 ,
}
INDENT_WIDTH
用于缩进的列数。
JOIN_MULTIPLE_LINES
将短行连接成一行。例如,单行
if
语句。
NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS
请勿在选定的二元运算符周围包含空格。例如:
1 + 2 * 3 - 4 / 5
当配置
*
,/
时,格式如下:
1 + 2 * 3 - 4 / 5
SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET
在列表的结束逗号和右括号之间插入空格等。
SPACE_INSIDE_BRACKETS
Use spaces inside brackets, braces, and parentheses. For example:
method_call ( 1 )
my_dict [ 3 ][ 1 ][ get_index ( * args , ** kwargs ) ]
my_set = { 1 , 2 , 3 }
SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN
设置为
True
可以在默认参数或关键字参数的赋值运算符周围使用空格。
SPACES_AROUND_DICT_DELIMITERS
在开头 '{' 之后和结尾 '}' 字典分隔符之前添加一个空格。
{ 1 : 2 }
将被格式化为:
{ 1 : 2 }
SPACES_AROUND_LIST_DELIMITERS
在开始“[”之后和结束“]”列表分隔符之前添加一个空格。
[ 1 , 2 ]
将被格式化为:
[ 1 , 2 ]
SPACES_AROUND_POWER_OPERATOR
设置为
True
更喜欢在**
周围使用空格。
SPACES_AROUND_SUBSCRIPT_COLON
在下标/切片运算符周围使用空格。例如:
my_list [ 1 : 10 : 2 ]
SPACES_AROUND_TUPLE_DELIMITERS
在开始的“(”之后和结束的“)”元组分隔符之前添加一个空格。
( 1 , 2 , 3 )
将被格式化为:
( 1 , 2 , 3 )
SPACES_BEFORE_COMMENT
尾随注释之前所需的空格数。这可以是单个值(表示每个尾随注释之前的空格数)或值列表(表示对齐列值;块中的尾随注释将与大于块内最大行长度的第一个列值对齐)堵塞)。
注意:在某些上下文中可能需要引用值列表(例如 shell 或编辑器配置文件)。
例如,使用
spaces_before_comment=5
:
1 + 1 # Adding values
将被格式化为:
1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment
与
spaces_before_comment="15, 20"
:
1 + 1 # Adding values
two + two # More adding
longer_statement # This is a longer statement
short # This is a shorter statement
a_very_long_statement_that_extends_beyond_the_final_column # Comment
short # This is a shorter statement
将被格式化为:
1 + 1 # Adding values <-- end of line comments in block aligned to col 15
two + two # More adding
longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20
short # This is a shorter statement
a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length
short # This is a shorter statement
SPLIT_ALL_COMMA_SEPARATED_VALUES
如果逗号分隔列表(
dict
、list
、tuple
或 functiondef
)位于太长的行上,则进行拆分,使每个元素位于单独的行上。
SPLIT_ALL_TOP_LEVEL_COMMA_SEPARATED_VALUES
SPLIT_ALL_COMMA_SEPARATED_VALUES
的变体,其中,如果带有逗号的子表达式适合其起始行,则不会拆分该子表达式。这可以避免像此代码中b
那样的拆分:
abcdef (
aReallyLongThing : int ,
b : [ Int ,
Int ])
使用新旋钮,它被分为:
abcdef (
aReallyLongThing : int ,
b : [ Int , Int ])
SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED
如果参数列表以逗号终止,则在参数之前分割。
SPLIT_BEFORE_ARITHMETIC_OPERATOR
设置为
True
会优先在+
、-
、*
、/
、//
或@
之前而不是之后进行分割。
SPLIT_BEFORE_BITWISE_OPERATOR
设置为
True
更喜欢在&
,|
之前拆分或^
而不是之后。
SPLIT_BEFORE_CLOSING_BRACKET
如果
list
或dict
文字不适合单行,则在右括号之前拆分。
SPLIT_BEFORE_DICT_SET_GENERATOR
在字典或集合生成器 (
comp_for
) 之前拆分。例如,请注意for
之前的拆分:
foo = {
variable : 'Hello world, have a nice day!'
for variable in bar if variable != 42
}
SPLIT_BEFORE_DOT
之前分裂
.
如果我们需要拆分更长的表达式:
foo = ( 'This is a really long string: {}, {}, {}, {}' . format ( a , b , c , d ))
会重新格式化为:
foo = ( 'This is a really long string: {}, {}, {}, {}'
. format ( a , b , c , d ))
SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN
如果表达式不适合放在一行中,则在包围表达式的左括号后进行拆分。
SPLIT_BEFORE_FIRST_ARGUMENT
如果要拆分参数/参数列表,请在第一个参数之前拆分。
SPLIT_BEFORE_LOGICAL_OPERATOR
设置为
True
表示更喜欢在and
或or
之后分割,而不是在之后分割。
SPLIT_BEFORE_NAMED_ASSIGNS
将命名作业拆分到单独的行上。
SPLIT_COMPLEX_COMPREHENSION
对于具有多个子句(例如多个
for
调用、if
过滤器表达式)且需要回流的列表推导式和生成器表达式,请将每个子句拆分到其自己的行上。例如:
result = [
a_var + b_var for a_var in xrange ( 1000 ) for b_var in xrange ( 1000 )
if a_var % b_var ]
会重新格式化为:
result = [
a_var + b_var
for a_var in xrange ( 1000 )
for b_var in xrange ( 1000 )
if a_var % b_var ]
SPLIT_PENALTY_AFTER_OPENING_BRACKET
开局后立即分裂的处罚。
SPLIT_PENALTY_AFTER_UNARY_OPERATOR
在一元运算符之后分割行的惩罚。
SPLIT_PENALTY_ARITHMETIC_OPERATOR
将行拆分为
+
、-
、*
、/
、//
、%
和@
运算符的代价。
SPLIT_PENALTY_BEFORE_IF_EXPR
在
if
表达式之前进行分割的惩罚。
SPLIT_PENALTY_BITWISE_OPERATOR
在
&
,|
周围分割线的惩罚、 和^
运算符。
SPLIT_PENALTY_COMPREHENSION
拆分列表理解或生成器表达式的惩罚。
SPLIT_PENALTY_EXCESS_CHARACTER
对超出列限制的字符的惩罚。
SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT
由于向逻辑行添加行分割而产生的损失。分线越多,处罚就越高。
SPLIT_PENALTY_IMPORT_NAMES
将
import as
名称的惩罚。例如:
from a_very_long_or_indented_module_name_yada_yad import ( long_argument_1 ,
long_argument_2 ,
long_argument_3 )
会重新格式化为:
from a_very_long_or_indented_module_name_yada_yad import (
long_argument_1 , long_argument_2 , long_argument_3 )
SPLIT_PENALTY_LOGICAL_OPERATOR
在
and
和or
运算符周围分割线的惩罚。
USE_TABS
使用制表符进行缩进。
YAPF 非常努力地确保格式正确。但对于某些代码,它不会像手动格式化那样好。特别是,在 YAPF 下,大数据文字可能会严重变形。
造成这种情况的原因是多方面的。简而言之,YAPF只是一个帮助开发的工具。它将格式化内容以与样式指南一致,但这可能不等于可读性。
缓解这种情况的方法是指示 YAPF 在重新格式化某些内容时应忽略的区域:
# yapf: disable
FOO = {
# ... some very large, complex data literal.
}
BAR = [
# ... another large data literal.
]
# yapf: enable
您还可以禁用单个文字的格式,如下所示:
BAZ = {
( 1 , 2 , 3 , 4 ),
( 5 , 6 , 7 , 8 ),
( 9 , 10 , 11 , 12 ),
} # yapf: disable
要保留漂亮的内缩右括号,请在您的样式中使用dedent_closing_brackets
。请注意,在这种情况下,所有括号(包括函数定义和调用)都将使用该样式。这提供了格式化代码库的一致性。
我们想使用 clang-format 的重新格式化算法。它非常强大,旨在提供最佳的格式。现有工具是根据不同的目标创建的,并且需要进行大量修改才能转换为使用 clang-format 的算法。
请这样做! YAPF 被设计为用作库和命令行工具。这意味着工具或 IDE 插件可以免费使用 YAPF。
YAPF 非常努力地完全符合 PEP 8。然而,最重要的是不要冒险改变代码的语义。因此,YAPF 尝试尽可能安全并且不更改令牌流(例如,通过添加括号)。然而,所有这些情况都可以轻松手动修复。例如,
from my_package import my_function_1 , my_function_2 , my_function_3 , my_function_4 , my_function_5
FOO = my_variable_1 + my_variable_2 + my_variable_3 + my_variable_4 + my_variable_5 + my_variable_6 + my_variable_7 + my_variable_8
不会被拆分,但您只需添加括号即可轻松获得正确结果:
from my_package import ( my_function_1 , my_function_2 , my_function_3 ,
my_function_4 , my_function_5 )
FOO = ( my_variable_1 + my_variable_2 + my_variable_3 + my_variable_4 +
my_variable_5 + my_variable_6 + my_variable_7 + my_variable_8 )
YAPF 中的主要数据结构是LogicalLine
对象。它包含一个FormatToken
s 列表,如果没有列限制,我们希望将其放在一行上。表达式语句中间的注释是一个例外,将强制该行被格式化为多行。格式化程序一次作用于一个LogicalLine
对象。
LogicalLine
通常不会影响其之前或之后的行的格式。该算法的一部分可以将两个或多个LogicalLine
s 连接成一行。例如,一个简短的 if-then 语句可以放在一行中:
if a == 42 : continue
YAPF 的格式化算法创建一个加权树,充当算法的解空间。树中的每个节点代表格式化决策的结果——即,是否在标记之前进行分割。每个格式决定都有与其相关的成本。因此,成本是在两个节点之间的边缘实现的。 (实际上,加权树没有单独的边缘对象,因此成本取决于节点本身。)
例如,采用以下 Python 代码片段。对于此示例,假设第 (1) 行违反了列限制并需要重新格式化。
def xxxxxxxxxxx ( aaaaaaaaaaaa , bbbbbbbbb , cccccccc , dddddddd , eeeeee ): # 1
pass # 2
对于第 (1) 行,算法将构建一棵树,其中每个节点( FormattingDecisionState
对象)都是该标记处的行的状态,并决定是否在该标记之前进行拆分。注意: FormatDecisionState
对象是按值复制的,因此图中的每个节点都是唯一的,其中一个节点的更改不会影响其他节点。
启发式用于确定分裂或不分裂的成本。由于节点在标记插入之前保存树的状态,因此它可以轻松确定分裂决策是否会违反其中一项样式要求。例如,当不在前一个标记和正在添加的标记之间进行分割时,启发式方法能够对边缘应用额外的惩罚。
在某些情况下,我们永远不想拆分行,因为这样做总是有害的(即,它将需要反斜杠换行符,这很少是可取的)。对于第 (1) 行,我们永远不想拆分前三个标记: def
、 xxxxxxxxxxx
和(
。我们也不想在末尾的)
和:
之间拆分。据说这些区域是“牢不可破的”。这在树中反映为在牢不可破的区域内不存在“分裂”决策(左手分支)。
现在我们有了树,我们通过查找成本最低的树路径来确定“最佳”格式。
就是这样!