Python 如何确保在编辑代码样式/空白时不更改逻辑?

Python 如何确保在编辑代码样式/空白时不更改逻辑?,python,python-3.x,refactoring,diff,Python,Python 3.x,Refactoring,Diff,在对Python脚本进行表面更改(例如更改代码样式/格式/空白)时,能够检查是否(意外)对代码进行了任何逻辑更改非常有用 对于C/C++,我生成汇编程序并加以区分(对于特定于平台的ifdef的,不是100%的傻瓜,但仍然很有用)。虽然我可以将一个pyc文件进行二进制区分,但这对于查看到底发生了什么变化并没有多大帮助 是否有一种方便的方法来获取AST的一些可读文本输出,以便检查更改 这当然可能会引起一些误报(例如,将str%bar替换为str.format(bar)),但我仍然有兴趣知道是否存在一

在对Python脚本进行表面更改(例如更改代码样式/格式/空白)时,能够检查是否(意外)对代码进行了任何逻辑更改非常有用

对于C/C++,我生成汇编程序并加以区分(对于特定于平台的
ifdef的
,不是100%的傻瓜,但仍然很有用)。虽然我可以将一个
pyc
文件进行二进制区分,但这对于查看到底发生了什么变化并没有多大帮助

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

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


背景信息

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


此外,您可能希望清理测试代码本身

您可以很容易地创建自己的树,我建议您阅读,如果树上的相等运算符已重载以进行比较,那么您必须手动比较它们


了解它

解析AST以检查逻辑差异是一个好主意。Python使使用它变得非常简单

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还有另一个。我们将源代码解析为AST,并计算出与AST的差异。这将忽略字符串中的空白格式、注释、数字基数和转义序列。SmartDifference适用于Python以及其他语言

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


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

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

例如:


有趣的是,这个问题被标记为
Python3.x
,这个工具只支持
Python2.x
时间已经改变。工具现在支持Python 3,以及各种其他语言和方言。
SmartDifferencer
web站点仍然将Python 2.6列为支持的版本。是时候更新网站了。(我们制作了很多工具,有时网站会落后)。谢谢你的提示。这很好,但所有内容都在一行,
ast.dump(tree).split(“,”)
-提供了一个相当好的快速简单的文本文件,可以进行缩进,但需要更多的工作。(ast.dump的代码为
py_to_ast < my_script.py > my_ast.txt
#!/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()