【问题标题】:Issue warning for missing comma between list items bug针对列表项错误之间缺少逗号发出警告
【发布时间】:2016-04-05 02:00:41
【问题描述】:

故事:

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

test = [
    "item1"
    "item2"
]

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

问题经常出现在重新排列列表中的项目之后。

存在此问题的示例堆栈溢出问题:

问题:

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

【问题讨论】:

  • 或者只检查列表中的空格分隔元素...您不需要换行符来解决这个问题。
  • 从技术上讲,这是一项功能,而不是错误。
  • FWIW,我通常用逗号终止多行容器文字中的最后一个项目,以防我想附加更多项目或重新排列现有项目。 IME,这往往会减少意外串联的机会。
  • @PM2Ring:啊,所以细节是:有了这个特性,我们不需要反斜杠来连接括号中的字符串。所以我们可以在'He' 之后使用print('He' <newline> 'llo') 而不用反斜杠。
  • 在列表的每一行都有一个逗号,除了减少这个问题之外,还有助于更清晰的差异。不过,答案很好,因为逗号确实会被遗忘。

标签: python string list multiline


【解决方案1】:

这个正则表达式会发现问题的出现。只需“搜索”项目中的所有文件即可。

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

https://regex101.com/ 和 NotePad++ 中测试

【讨论】:

    【解决方案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']]"
    

    在现实生活中,我宁愿避免犯这样的错误;有像 Sublime Text 这样的优秀 IDE,它允许您使用多光标编辑和格式化列表。如果您习惯了这些概念,这些“分离”错误就不会在您的代码中发生。

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

    【讨论】:

      【解决方案3】:

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

      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()Py3 中只有一个参数readline,这是一种在类文件对象上的方法,它本质上为类文件对象(relevant answer)返回下一行。它返回一个共有 5 个元素的命名元组,其中包含有关令牌类型、令牌字符串以及行号和行中位置的信息。

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

      这种方法的好处在于它可以很直接地概括。为了适应字符串文字连接发生的所有情况(即,在“分组”运算符 (), {}, [] 内),您检查令牌是否为 type = 51(或 53 for Python 3)或是否存在 (, [, { 中的任何值在同一行上(这些是粗略的,最重要的建议 atm)。

      现在,我不太确定其他人是如何解决这些问题的 看起来你可以研究一下tokenize 提供了所有必要的信息,唯一缺少的就是检测它的逻辑。

      实施说明:这些值(例如,type)在版本之间确实不同,并且可能会发生变化,因此应该注意这一点。不过,人们可能会利用这个by only working with constants 来获取令牌。


      使用parserast

      另一个可能更乏味的解决方案可能涉及parserast 模块。字符串的连接实际上是在抽象语法树的创建过程中执行的,因此您也可以在那里检测它。

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

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

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

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

      所以,这是另一种可能的方法,但正如我之前提到的,它更乏味。我不确定线路信息是否可用并且您处理两个不同的模块。如果您可能想在编译器链中使用更高的内部对象,请将其作为一个回想。


      结束评论:作为结束说明,tokenize 方法在这种情况下似乎是最合乎逻辑的。相反,似乎pylint 实际上与astroid 一起工作,这是一个简化python 代码抽象语法树分析的python 库。所以,理想情况下应该看看它以及它是如何使用的inside pylint

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

      【讨论】:

      • 很棒的asttokenize 样品!从中制定一个 pylint 规则并提议将其合并可能是一个好主意。我会玩弄它。谢谢!
      • 我真的不知道 pylint 之类的工具是如何实现的,但发现会很有趣。是的,tokenize 在这种特定情况下非常棒,您可以通过所有这些模块获得大量洞察力,让您轻松“窥视”并查看正在发生的事情。如果您这样做,请回来发布您的想法,我想看看您决定采取的方法。
      • 在提出之前,应该改进 pylint 的想法。如果第一个字符串以“\n”或空格结尾,或者如果第二个字符串太长以至于无法放入前一行,则可能不需要警告,并且有意连接字符串。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-21
      • 1970-01-01
      • 1970-01-01
      • 2012-03-04
      • 1970-01-01
      • 2018-01-10
      • 1970-01-01
      相关资源
      最近更新 更多