编辑代码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()
当对 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()