发出列表项错误之间缺少逗号的警告

时间:2015-12-31 03:30:22

标签: python string list multiline

故事:

当在多行上定义字符串列表时,通常很容易忘记列表项之间的逗号,如下例所示:

test = [
    "item1"
    "item2"
]

列表test现在只有一个项目"item1item2"

重新排列列表中的项目后,通常会出现问题。

具有此问题的Stack Overflow示例问题:

问题:

有没有办法,最好使用静态代码分析,在这种情况下发出警告,以便尽早发现问题?

3 个答案:

答案 0 :(得分:19)

这些只是可能的解决方案,因为我不太适合静态分析

使用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 ','标记,表示存在由逗号分隔的第二个元素。

根据这些信息,我们可以在generate_tokens模块中使用非常方便的方法tokenizetokenize.tokenize()中的方法tokenize.generate_tokens()Py3有一个参数readline,一个类似文件的对象的方法,它基本上返回该文件的下一行,如object({{ 3}})。它返回一个带有5个元素的命名元组,其中包含有关令牌类型,令牌字符串以及行号和行中位置的信息。

使用此信息,理论上可以循环一个文件,并且当列表初始化中不存在OP ','时(通过检查标记NAMEOP '='OP '['存在于同一行号上)可以在检测到它的行上发出警告。

这种方法的好处在于,概括起来非常简单。要适应字符串文字串联发生的所有情况(即,在'分组'运算符(), {}, []内),您检查该标记是relevant answer(或type = 51)或者(, [, {中任何一个值存在于同一行(这些是粗略的,顶部的建议atm)。

现在,我不确定其他人是如何解决这些问题的 但是 似乎它可能是你可以做的事情看看。所有必要的信息都由tokenize提供,检测它的逻辑是唯一缺失的。

实施注意事项:这些值(例如,type)在版本之间确实有所不同,并且可能会发生变化,因此需要注意这些值。但是,人们可以利用这个53 for Python 3来代替令牌。

使用parserast

可能更繁琐的另一个可能的解决方案可能涉及by only working with constantsparser模块。字符串的串联实际上是在创建抽象语法树期间执行的,因此您可以在那里检测它。

我真的不想转储我要提及的parserast方法的完整输出,但是,只是为了确保我们&#39 ;在同一页面上,我将使用以下列表初始化语句:

l_init = """
test = [
    "item1"
    "item2",
    "item3"
]
"""

为了生成解析树,请使用ast。完成此操作后,您可以使用p = parser.suite(l_init)查看它(输出太大而无法添加)。您注意到的是三个不同的str对象item1item2item3 将有三个条目。

另一方面,当使用p.tolist()创建AST并使用node = ast.parse(l_init) 查看时,只有两个条目:一个用于连接的str s item1item2,另一个用于item3

所以,这是另一种可行的方法,但正如我之前提到的那样,它更加乏味。我不确定行信息是否可用,您是否处理两个不同的模块。如果您想要在编译器链中更高级地使用内部对象,请将其作为背景思考。

结束评论:作为结束语,在这种情况下,tokenize方法似乎是最合乎逻辑的方法。相反,似乎pylint实际上与ast.dump(node) python库一起使用,它简化了对Python代码的抽象语法树的分析。因此,理想情况下应该查看它以及如何使用astroid

注意当然,我可能会完全过度分析它,并且可以更简单地检查空格或换行符'你们建议的解决方案就足够了。 : - )

答案 1 :(得分:2)

我根据@Jim的帖子实现了代码。它可以适用于所有情况:

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,它允许你用多个光标编辑和格式化列表。如果你习惯了这些概念,这些"分离"错误不会在您的代码中发生。

当然,如果有一个开发人员团队,可以将这样的工具集成到测试环境中。

答案 2 :(得分:1)

此正则表达式将发现问题的发生。只需搜索所有'项目中的文件。

\[("[^"]*",[\s]*)*"[^"]*"[\s]*"

https://regex101.com/和NotePad ++

中测试过