针对列表项错误之间缺少逗号发出警告
Issue warning for missing comma between list items bug
故事:
当在多行上定义字符串列表时,通常很容易忘记列表项之间的逗号,如本例所示:
test = [
"item1"
"item2"
]
列表 test
现在只有一个项目 "item1item2"
。
问题经常出现在重新排列列表中的项目后。
有此问题的示例堆栈溢出问题:
- Python - Syntax error on colon in list
问题:
有没有办法,最好使用静态代码分析,在这种情况下发出警告以便尽早发现问题?
这些只是可能的解决方案,因为我不太适合 static-analysis。
与tokenize
:
我最近摆弄了 with tokenizing python code,我相信在添加足够的逻辑后,它具有执行此类检查所需的所有信息。对于您给定的列表,使用 python -m tokenize list1.py
生成的标记如下:
python -m tokenize list1.py
1,0-1,4: NAME 'test'
1,5-1,6: OP '='
1,7-1,8: OP '['
1,8-1,9: NL '\n'
2,1-2,8: STRING '"item1"'
2,8-2,9: NL '\n'
3,1-3,8: STRING '"item2"'
3,8-3,9: NL '\n'
4,0-4,1: OP ']'
4,1-4,2: NEWLINE '\n'
5,0-5,0: ENDMARKER ''
这当然是“有问题的”情况,内容将被串联起来。在 ,
存在的情况下,输出略有变化以反映这一点(我仅为列表主体添加了标记):
1,7-1,8: OP '['
1,8-1,9: NL '\n'
2,1-2,8: STRING '"item1"'
2,8-2,9: OP ','
2,9-2,10: NL '\n'
3,1-3,8: STRING '"item2"'
3,8-3,9: NL '\n'
4,0-4,1: OP ']'
现在我们有了额外的 OP ','
标记,表示存在以逗号分隔的第二个元素。
根据这些信息,我们可以使用 tokenize
模块中非常方便的方法 generate_tokens
。方法tokenize.generate_tokens()
, tokenize.tokenize()
in Py3
, has a single argument readline
, a method on file-like objects which essentially returns the next line for that file like object ()。它 returns 一个命名的元组,总共有 5 个元素,包含有关标记类型的信息、标记字符串以及行号和行中的位置。
使用此信息,理论上可以循环遍历文件,并且当列表初始化中不存在 OP ','
时(通过检查标记 NAME
、OP '='
和 OP '['
存在于同一行号),可以在检测到它的行上发出警告。
这种方法的好处是它很容易 straight-forward 概括。为了适应发生字符串文字连接的所有情况(即,在 'grouping' 运算符 (), {}, []
内),您检查令牌是否属于 type = 51
(or 53 for Python 3)或任何 [=29 中的值=] 存在于同一条线上(这些是粗略的,最重要的建议 atm)。
现在,我不太确定其他人是如何处理这些问题的 但是 它看起来它可能是你可以研究的东西。 tokenize
提供了所有必要的信息,唯一缺少的是检测它的逻辑。
实施注意事项:这些值(例如,type
)在不同版本之间确实有所不同,并且可能会发生变化,因此应该注意这一点。不过,人们可能会利用这个 by only working with constants 作为代币。
与 parser
和 ast
:
另一个可能更乏味的解决方案可能涉及 parser
and ast
模块。字符串的连接实际上是在抽象语法树的创建过程中执行的,因此您也可以在那里检测它。
我真的不想转储我要提到的 parser
和 ast
方法的完整输出,但是,只是为了确保我们在同一页,我将使用以下列表初始化语句:
l_init = """
test = [
"item1"
"item2",
"item3"
]
"""
为了生成解析树,使用p = parser.suite(l_init)
. After this is done, you can get a view of it with p.tolist()
(输出太大无法添加)。您注意到 三个不同的 str
对象将有三个条目 item1
、item2
、item3
.
另一方面,当使用 node = ast.parse(l_init)
and viewed with ast.dump(node)
创建 AST 时,只有两个条目 :一个用于连接 str
s item1item2
和另一个条目 item3
.
所以,这是另一种可行的方法,但正如我之前提到的,它更乏味。我不确定行信息是否可用并且您处理两个不同的模块。如果您可能想在编译器链中较高的位置使用内部对象,请将其作为后顾之忧。
结束评论:作为结束语,在这种情况下,tokenize
方法似乎是最合乎逻辑的方法。相反,似乎 pylint
实际上适用于 astroid
a python lib that eases analysis of Abstract Syntax Trees for python code. So, one should ideally look at it and how it is used inside pylint.
注意:当然,我可能完全over-analyzing它和一个更简单的'check for white-space or newline'解决方案你们建议就足够了。 :-)
我基于@Jim 的 post 实现了代码。愿它适用于所有情况:
import tokenize
from io import BytesIO
def my_checker(pycode):
"""
tokenizes python code and yields
start, end, strline of any position where
a scenario like this happens (missing string seperator):
[..., "a string" "derp", ...]
"""
IDLE = 0
WAITING_STRING = 1
CHECKING_SEPARATOR = 2
tokenizer = tokenize.tokenize(BytesIO(pycode.encode('utf-8')).readline)
state = IDLE
for toknum, tokval, start, end, strcode in tokenizer:
if state == IDLE:
if toknum == tokenize.OP and tokval == '[':
state = WAITING_STRING
elif state == WAITING_STRING:
if toknum == tokenize.STRING:
state = CHECKING_SEPARATOR
elif toknum == tokenize.OP and tokval == [']']:
state = IDLE
elif state == CHECKING_SEPARATOR:
if toknum == tokenize.STRING:
yield (start, end, strcode)
elif toknum == tokenize.OP and tokval in ['+', ',']:
state = WAITING_STRING
elif toknum == tokenize.OP and tokval == ']':
state = IDLE
my_code = """
foo = "derp"
def derp(a,x):
return str('dingdong'+str(a*x))
[
"derp"+"FOO22" , "FOO", "donk" "slurp",0, 0
]
class extreme_logical_class():
STATIC_BAD_LIST = [0,
"BLA,",
"FOO"
"derp"
]
def __init__(self):
self._in_method_check = ["A" "B"]
nested_list = [
['DERP','FOO'],
[0,'hello', 'peter' 'pan'],
['this', 'is', ['ultra', 'mega'
'nested']]
]
"""
for error in my_checker(my_code):
print('missing , in list at: line {}@{} to line {}@{}: "{}"'.format(
error[0][0],error[0][1],error[1][0],error[1][1], error[2].strip()
))
结果是:
keksnicoh@localhost ~ % python3 find_bad_lists.py
missing , in list at: line 6@36 to line 6@43: ""derp"+"FOO22" , "FOO", "donk" "blurp",0 0"
missing , in list at: line 13@8 to line 13@14: ""derp""
missing , in list at: line 16@37 to line 16@40: "self._in_method_check = ["A" "B"]"
missing , in list at: line 20@24 to line 20@29: "[0,'hello', 'peter' 'pan'],"
missing , in list at: line 22@8 to line 22@16: "'nested']]"
在现实生活中我宁愿避免犯这样的错误;有很好的 IDE 之类的 Sublime Text,它允许您使用多光标编辑和格式化列表。如果您习惯了这些概念,这些 "separation" 错误就不会出现在您的代码中。
当然,如果有一个开发团队,可以将这样的工具集成到测试环境中。
此正则表达式将查找出现的问题。您的项目中只有 'search all' 个文件。
\[("[^"]*",[\s]*)*"[^"]*"[\s]*"
在 https://regex101.com/ 和 NotePad++
中测试
故事:
当在多行上定义字符串列表时,通常很容易忘记列表项之间的逗号,如本例所示:
test = [
"item1"
"item2"
]
列表 test
现在只有一个项目 "item1item2"
。
问题经常出现在重新排列列表中的项目后。
有此问题的示例堆栈溢出问题:
- Python - Syntax error on colon in list
问题:
有没有办法,最好使用静态代码分析,在这种情况下发出警告以便尽早发现问题?
这些只是可能的解决方案,因为我不太适合 static-analysis。
与tokenize
:
我最近摆弄了 with tokenizing python code,我相信在添加足够的逻辑后,它具有执行此类检查所需的所有信息。对于您给定的列表,使用 python -m tokenize list1.py
生成的标记如下:
python -m tokenize list1.py
1,0-1,4: NAME 'test'
1,5-1,6: OP '='
1,7-1,8: OP '['
1,8-1,9: NL '\n'
2,1-2,8: STRING '"item1"'
2,8-2,9: NL '\n'
3,1-3,8: STRING '"item2"'
3,8-3,9: NL '\n'
4,0-4,1: OP ']'
4,1-4,2: NEWLINE '\n'
5,0-5,0: ENDMARKER ''
这当然是“有问题的”情况,内容将被串联起来。在 ,
存在的情况下,输出略有变化以反映这一点(我仅为列表主体添加了标记):
1,7-1,8: OP '['
1,8-1,9: NL '\n'
2,1-2,8: STRING '"item1"'
2,8-2,9: OP ','
2,9-2,10: NL '\n'
3,1-3,8: STRING '"item2"'
3,8-3,9: NL '\n'
4,0-4,1: OP ']'
现在我们有了额外的 OP ','
标记,表示存在以逗号分隔的第二个元素。
根据这些信息,我们可以使用 tokenize
模块中非常方便的方法 generate_tokens
。方法tokenize.generate_tokens()
, tokenize.tokenize()
in Py3
, has a single argument readline
, a method on file-like objects which essentially returns the next line for that file like object (
使用此信息,理论上可以循环遍历文件,并且当列表初始化中不存在 OP ','
时(通过检查标记 NAME
、OP '='
和 OP '['
存在于同一行号),可以在检测到它的行上发出警告。
这种方法的好处是它很容易 straight-forward 概括。为了适应发生字符串文字连接的所有情况(即,在 'grouping' 运算符 (), {}, []
内),您检查令牌是否属于 type = 51
(or 53 for Python 3)或任何 [=29 中的值=] 存在于同一条线上(这些是粗略的,最重要的建议 atm)。
现在,我不太确定其他人是如何处理这些问题的 但是 它看起来它可能是你可以研究的东西。 tokenize
提供了所有必要的信息,唯一缺少的是检测它的逻辑。
实施注意事项:这些值(例如,type
)在不同版本之间确实有所不同,并且可能会发生变化,因此应该注意这一点。不过,人们可能会利用这个 by only working with constants 作为代币。
与 parser
和 ast
:
另一个可能更乏味的解决方案可能涉及 parser
and ast
模块。字符串的连接实际上是在抽象语法树的创建过程中执行的,因此您也可以在那里检测它。
我真的不想转储我要提到的 parser
和 ast
方法的完整输出,但是,只是为了确保我们在同一页,我将使用以下列表初始化语句:
l_init = """
test = [
"item1"
"item2",
"item3"
]
"""
为了生成解析树,使用p = parser.suite(l_init)
. After this is done, you can get a view of it with p.tolist()
(输出太大无法添加)。您注意到 三个不同的 str
对象将有三个条目 item1
、item2
、item3
.
另一方面,当使用 node = ast.parse(l_init)
and viewed with ast.dump(node)
创建 AST 时,只有两个条目 :一个用于连接 str
s item1item2
和另一个条目 item3
.
所以,这是另一种可行的方法,但正如我之前提到的,它更乏味。我不确定行信息是否可用并且您处理两个不同的模块。如果您可能想在编译器链中较高的位置使用内部对象,请将其作为后顾之忧。
结束评论:作为结束语,在这种情况下,tokenize
方法似乎是最合乎逻辑的方法。相反,似乎 pylint
实际上适用于 astroid
a python lib that eases analysis of Abstract Syntax Trees for python code. So, one should ideally look at it and how it is used inside pylint.
注意:当然,我可能完全over-analyzing它和一个更简单的'check for white-space or newline'解决方案你们建议就足够了。 :-)
我基于@Jim 的 post 实现了代码。愿它适用于所有情况:
import tokenize
from io import BytesIO
def my_checker(pycode):
"""
tokenizes python code and yields
start, end, strline of any position where
a scenario like this happens (missing string seperator):
[..., "a string" "derp", ...]
"""
IDLE = 0
WAITING_STRING = 1
CHECKING_SEPARATOR = 2
tokenizer = tokenize.tokenize(BytesIO(pycode.encode('utf-8')).readline)
state = IDLE
for toknum, tokval, start, end, strcode in tokenizer:
if state == IDLE:
if toknum == tokenize.OP and tokval == '[':
state = WAITING_STRING
elif state == WAITING_STRING:
if toknum == tokenize.STRING:
state = CHECKING_SEPARATOR
elif toknum == tokenize.OP and tokval == [']']:
state = IDLE
elif state == CHECKING_SEPARATOR:
if toknum == tokenize.STRING:
yield (start, end, strcode)
elif toknum == tokenize.OP and tokval in ['+', ',']:
state = WAITING_STRING
elif toknum == tokenize.OP and tokval == ']':
state = IDLE
my_code = """
foo = "derp"
def derp(a,x):
return str('dingdong'+str(a*x))
[
"derp"+"FOO22" , "FOO", "donk" "slurp",0, 0
]
class extreme_logical_class():
STATIC_BAD_LIST = [0,
"BLA,",
"FOO"
"derp"
]
def __init__(self):
self._in_method_check = ["A" "B"]
nested_list = [
['DERP','FOO'],
[0,'hello', 'peter' 'pan'],
['this', 'is', ['ultra', 'mega'
'nested']]
]
"""
for error in my_checker(my_code):
print('missing , in list at: line {}@{} to line {}@{}: "{}"'.format(
error[0][0],error[0][1],error[1][0],error[1][1], error[2].strip()
))
结果是:
keksnicoh@localhost ~ % python3 find_bad_lists.py
missing , in list at: line 6@36 to line 6@43: ""derp"+"FOO22" , "FOO", "donk" "blurp",0 0"
missing , in list at: line 13@8 to line 13@14: ""derp""
missing , in list at: line 16@37 to line 16@40: "self._in_method_check = ["A" "B"]"
missing , in list at: line 20@24 to line 20@29: "[0,'hello', 'peter' 'pan'],"
missing , in list at: line 22@8 to line 22@16: "'nested']]"
在现实生活中我宁愿避免犯这样的错误;有很好的 IDE 之类的 Sublime Text,它允许您使用多光标编辑和格式化列表。如果您习惯了这些概念,这些 "separation" 错误就不会出现在您的代码中。
当然,如果有一个开发团队,可以将这样的工具集成到测试环境中。
此正则表达式将查找出现的问题。您的项目中只有 'search all' 个文件。
\[("[^"]*",[\s]*)*"[^"]*"[\s]*"
在 https://regex101.com/ 和 NotePad++
中测试