编辑代码style/whitespace如何保证逻辑不发生变化?

How to ensure no logic changes when editing code style/whitespace?

当对 Python 脚本进行表面更改时(例如,更改 code-style/formatting/whitespace),能够检查是否(意外地)对代码进行了任何逻辑更改是很有用的。

对于 C/C++,我生成了汇编程序并对其进行了比较 (对于特定平台 ifdef's,并非 100% 万无一失,但仍然有用)。虽然我可以对 pyc 文件进行二进制比较,但这对查看到底发生了什么变化没有太大帮助。

有没有一种方便的方法来获取 AST 的一些人类可读的文本输出,以便检查更改?

这当然可能会引起一些误报(例如,将 str % bar 替换为 str.format(bar)),但我仍然想知道是否存在一些方便的方法。


背景资料

因为建议只 运行 测试 。 这是我为什么问这个问题的一些背景。 这段代码没有测试,而且它不太可能有 100% 的测试覆盖率,因为它恰好是构建系统实用程序脚本。理论上,我们可以花时间添加一个测试套件,并找到在不同平台上启用测试 运行 的方法(猴子补丁 sys.platform 或 运行 在 VM 中所有支持的平台上持续集成...) 但我们现在根本无法证明花费这种努力是合理的。

此外,您可能想要清理测试代码本身!

你可以很容易地制作你自己的树,我建议你阅读它上面的相等运算符是否已经过载以进行比较,它没有你必须手动比较它们。

了解一下 here

解析 AST 以检查逻辑差异是个好主意。 Python 让 its AST 的工作变得异常简单。

import ast

original_ast = ast.parse("""
import sys
for a in range(0,10):
    print(a)
sys.exit(0)""")

altered_ast = ast.parse("""
import sys
for a in range(0,10):
    print(a + 1)
sys.exit(0)""")

ast.dump(original_ast) == ast.dump(altered_ast)

如果您想查看差异,那么 Python 还有另一个 built in diff library

我们的 SmartDifferencer 将源代码解析为 AST,并计算 AST 的差异。这会忽略字符串中的空格格式、注释、数字基数、转义序列。 SmartDifference 适用于 Python 以及其他语言。

如果唯一的变化是空格差异,则 AST 差异为空。在这种情况下,程序在语义上是相同的。

任何实际差异都以对代码结构进行合理编辑的形式报告,例如:删除、插入、移动、复制、替换标识符。这些类型的增量比 diff 的“行删除”、“行插入”增量更容易理解。

正如@erik-e 指出的那样,您可以简单地使用 ast.dump,但是这会将所有内容放在一行中,这是 ast.dump 的修改版本,在从标准输入读取的脚本中并打印出 ast.

例如:

py_to_ast < my_script.py > my_ast.txt

除换行和缩进外,输出与ast.dump相同。

您可以从以下网址下载脚本: https://bitbucket.org/ideasman42/dotfiles/src/master/bin/py_to_ast.py

Example output 这个脚本 运行 通过它自身

#!/usr/bin/env python3

import ast


def dump(node, annotate_fields=True, include_attributes=False):
    """
    ast.dump from Python3.4 modified for pretty printing.
    """
    from ast import AST, iter_fields

    def _format(node, level):
        level_next = level + 1
        indent = level * '  '
        indent_next = level_next * '  '
        if isinstance(node, AST):
            fields = [(a, _format(b, level_next)) for a, b in iter_fields(node)]
            rv = '\n' + indent + '%s(%s' % (node.__class__.__name__, (',\n' + indent_next).join(
                ('%s=%s' % field for field in fields)
                if annotate_fields else
                (b for a, b in fields)
            ))

            if include_attributes and node._attributes:
                rv += fields and ', ' or ' '
                rv += (',\n' + indent_next).join('%s=%s' % (a, _format(getattr(node, a), level_next))
                                                 for a in node._attributes)
            return rv + ')'
        elif isinstance(node, list):
            return '[%s]' % (',\n' + indent_next).join(_format(x, level_next) for x in node)
        return repr(node)
    if not isinstance(node, AST):
        raise TypeError('expected AST, got %r' % node.__class__.__name__)
    return _format(node, 0)


import sys

def main():
    data = sys.stdin.read()
    tree = ast.parse(data)
    print(dump(tree))

if __name__ == "__main__":
    main()