Python中的`goto`
我必须在Python中使用goto
。我找到了一些资料,比如entrian的goto
,但我的Python版本(Mac上的CPython 2.7.1)没有这个模块,所以看起来不太通用。理论上,它应该能在所有支持CPython字节码的Python实现中工作(尤其是我关心的CPython和PyPy)。还有这个相关问题,以及cdjc的goto
,还有下面回答中提到的其他方法。
我可以手动构建字节码(也就是自己写一个Python编译器),因为有这样的指令(JUMP_ABSOLUTE
等)。但我在想有没有更简单的方法。是否可以通过inspect
之类的工具调用单个字节码指令?我还考虑过通过Python编译,然后自动修补生成的Python字节码。
当然,人们会问我为什么,如果我不解释我真的非常需要这个,可能就得不到任何有用的回答。简单来说,我的使用场景是:我正在把一个C语言的抽象语法树(AST)翻译成Python的抽象语法树,并进行编译。我可以把每个逻辑流程(所有的循环和其他东西)以某种方式映射到等效的Python代码。除了goto
以外,其他的都可以。相关项目有:PyCParser(见interpreter.py
),PyCPython,PyLua。
6 个回答
我更新了我的 Python goto 装饰器,适用于 Python 3。你可以在这里找到它:https://github.com/cdjc/goto。使用 goto 代替函数可以让状态机的运行速度快大约五倍。
Python 2 的版本仍然可以在这里找到:http://code.activestate.com/recipes/576944-the-goto-decorator/,不过这个版本有一些错误,而这些错误在 Python 3 的版本中已经修复了。
你可能是我见过的唯一一个需要在Python中使用goto
的合理例子。:-)
在Python中模拟前向goto
最简单的方法是使用异常,因为异常可以跳出任何深度的嵌套控制结构。
class Goto(Exception):
pass
try:
if foo = "bar":
raise Goto
print "foo is not bar"
except Goto:
print "foo is bar"
如果你需要支持多个目标,这就变得复杂了,不过我觉得可以通过嵌套的try/except
结构和多种异常类来实现,每个目标对应一种异常。由于C语言将goto
限制在单个函数的范围内,至少你不用担心如何在不同函数之间使用它。:-) 当然,这种方法不适用于反向goto
。
另外要注意的是,虽然Python中的异常处理比某些语言快,但仍然比正常的控制结构(比如while
和for
)要慢。
这可能会需要很多工作(不过也许不比你现在要做的更多),但如果你能生成Python字节码而不是Python源代码,那么实现goto
就没问题了,因为Python字节码(就像大多数伪机器语言一样)有一个非常合适的JUMP_ABSOLUTE
操作码。
我知道大家在想什么:
不过,有些情况下你可能真的需要用到 goto
。
这个 Python 的小技巧提供了一个将 goto
命令作为函数装饰器的方式。
goto 装饰器 (这是 Carl Cerecke 的 Python 小技巧)
如果你对现有的
goto
模块的速度感到厌烦,这个小技巧就是为你准备的。这个小技巧中的goto
快了大约 60 倍,而且代码也更简洁(使用sys.settrace
看起来不太符合 Python 的风格)。因为这是一个装饰器,它能让读者知道哪些函数使用了goto
。它没有实现 comefrom 命令,虽然扩展它来实现这个功能并不难(留给读者自己去做)。另外,计算得到的 goto 也不支持;这在 Python 中不太常见。
- 使用
dis.dis(fn)
可以显示一个函数的字节码反汇编。- 函数的字节码可以通过
fn.func_code.co_code
访问。这是只读的,所以:- 被装饰的函数和旧的函数完全一样,但字节码会更新以遵循
goto
命令。- 这个小技巧只适用于 Python 2.x;在 Python 3.x 中没有这个新模块(另一个留给读者的练习!)
用法
@goto
def test1(n):
s = 0
label .myLoop
if n <= 0:
return s
s += n
n -= 1
goto .myLoop
>>> test1(10)
55
更新
这里有两个与 Python 3 兼容的额外实现: