Python 如何确保在编辑代码样式/空白时不更改逻辑?
在对Python脚本进行表面更改(例如更改代码样式/格式/空白)时,能够检查是否(意外)对代码进行了任何逻辑更改非常有用 对于C/C++,我生成汇编程序并加以区分(对于特定于平台的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)),但我仍然有兴趣知道是否存在一
ifdef的,不是100%的傻瓜,但仍然很有用)。虽然我可以将一个pyc
文件进行二进制区分,但这对于查看到底发生了什么变化并没有多大帮助
是否有一种方便的方法来获取AST的一些可读文本输出,以便检查更改
这当然可能会引起一些误报(例如,将str%bar
替换为str.format(bar)
),但我仍然有兴趣知道是否存在一些方便的方法
背景信息
因为有人建议只运行测试。
以下是我为什么问这个问题的一些背景。
这段代码没有测试,而且不可能有100%的测试覆盖率,因为它恰好是构建系统实用程序脚本。理论上,我们可以花时间添加一个测试套件,并找到使测试能够在不同平台上运行的方法(monkey patchsys.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()