Python中的`goto`

时间:2011-08-05 16:15:57

标签: python compilation bytecode goto

我必须在Python中使用goto。我发现entrians goto但我的Python实现(Mac上的CPython 2.7.1)没有这个模块,因此它似乎不可移植。它至少应该适用于支持CPython字节码的所有Python实现(特别是我关心CPython和PyPy)。然后是this related questioncdjc's goto。以下是答案给出的那些。

我可以手动构建字节码(即编写我自己的Python编译器)因为有这样的指令(JUMP_ABSOLUTE和朋友)。但我想知道是否有更简单的方法。是否可以通过inspect左右来调用单个字节码指令?我还考虑过通过Python编译,然后自动修补生成的Python字节码。


当然,如果我不解释为什么我真的需要这个,人们会问为什么,不会给我任何有用的答案。所以简而言之我的用例:我正在将一个C AST翻译成Python AST并编译它。我可以用某种方式将每个逻辑流(所有循环和其他东西)映射到等效的Python代码。除goto以外的所有内容。相关项目:PyCParser(请参阅interpreter.py),PyCPythonPyLua

6 个答案:

答案 0 :(得分:44)

我知道每个人都在想什么:

xkcd GOTO

但是,可能存在一些实际需要goto的教学案例。

这个python配方提供goto命令作为函数装饰器。

The goto decorator(由Carl Cerecke撰写的 Python配方

  

如果你厌倦了慢速,这就是你的秘诀   现有的goto模块http://entrian.com/goto/。这里的goto   配方快约60倍,也更清洁(滥用sys.settrace   似乎很难pythonic)。因为这是一个装饰器,它会提醒你   功能使用goto的阅读器。它没有实现来自   命令,虽然不难扩展它(运动   对于读者)。此外,不支持计算的gotos;他们不是   Python化。

     
      
  • 使用dis.dis(fn)显示函数的字节码反汇编。
  •   
  • fn.func_code.co_code访问函数的字节码。   这是只读的:
  •   
  • 装饰函数的创建与旧函数完全相同,   但更新了字节码以遵守goto命令。
  •   
  • 这只是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兼容的实现:

答案 1 :(得分:8)

您可能拥有我在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中的异常虽然与某些语言相比速度较快,但仍然比whilefor等常规流控制结构慢。

这可能是很多工作(尽管可能不会比你已经做的更多)但是如果你可以生成Python字节码而不是Python源代码,那么实现goto就没有问题,因为Python字节码(与大多数伪机器语言一样)具有完美的JUMP_ABSOLUTE操作码。

答案 2 :(得分:5)

我已经为Python 3更新了我的python goto decorator。你可以在https://github.com/cdjc/goto获得它。使用goto而不是函数可以使状态机快5倍。

python 2的版本仍然可以在http://code.activestate.com/recipes/576944-the-goto-decorator/获得,但它有许多在python 3版本中修复的错误。

答案 3 :(得分:1)

使用goto的代码可能会遵循一些常见的模式。

在大多数情况下,我怀疑所有goto语句都会跳转到稍后的位置,并且位于更封闭的块中;如果函数体完美地遵循这种模式,则将goto变换为异常,将标签作为except块。

goto在同一个块中从一个地方跳到另一个地方的其他情况,就像在状态机中使用的那样。这可能会被转换为调度循环;标签和下一个标签之间的每个区域都成为一个功能; goto被next_state = 'labelname'; return

取代

最后一种情况,既不是上述情况,也可能是非平凡的情况,就是当跳转进入循环体时。我还没有答案。

答案 4 :(得分:1)

这不是你想要的,但是听我说。

很多年前,我和儿子在BASIC写了一个“冒险”游戏。地下游戏中的每个位置都是一个行号。例如,当您通过隧道向北离开一个位置时,您到达了另一个位置。

编码类似于if response == 'N' GOTO 2400。因此,玩家最终使用GOTO遍布整个地方。

我想知道如何在Python中完成这项工作并想出这个。

也许这种技术可以用于需要类似GOTO的其他应用程序。如果将程序拆分为函数块,下面的“有点傻”编码就可以了。

""" Simple, short and unfinished 'Adventure' game to show how a program such
as this with 'locations' (where each location is handled by a function) can
simulate 'GOTO's through use of the eval() function. Each time the player
chooses an exit from his current location, where he goes to will depend on the
exit chosen and achieved using eval() on the last line.

This saves having to code a series of 'if's at each location which call the
correct function the player goes to. Also, because the code always comes back
to the eval line at the botton each time, one location's function doesn't call
the next location's function, with possible risk of stack overflow if the
program is radically extended.

The program uses randint() to determine if characters are there and what they
are doing. This is just a taster. Dramatic improvements could be made if the
player could collect and use artefacts found during his travels. For instance
if there was a key somewhere and it was collected, it could be used to unlock
the door, but the key can't be picked up unless the troll isn't there etc.
The program needs to be able to parse (understand) simple natural language
(English) commands such as 'take key' or 'unlock door' or 'give food to troll'
There will also need to be some global variables so each function can behave
and do stuff dependent on these variables.

The program needs to be able to respond to players' commands, such as after a
player has indicated which direction he wants to go, the program responds,
'You can't go that way. the Ork is blocking your path'. You get the picture.

The program also needs to be able to save variables in a dictionary (which is
then pickled into a file) so players can close the game and save it and pick up
where they left off next time.

People new to this sort of game should realise by the way, that just because
a tunnel (or other route) leaves one location northwards, that doesn't mean
that it arrives at the next location from the south. The tunnels twist and
turn all over the place."""


def l0():
    #print('L0')
    print("You're south of a forbidding-looking cave")
    go = input('n or q > ')
    if go == 'n': return('1')
    if go == 'q': return('q')
    else: return 'q'

def l1():
    #print('L1')
    print("You're in a large, dark cave. Bats are hanging from the ceiling.")
    print("Tunnels lead north, east and west. The entrance is south of you.")
    go = input('n s e w > ')
    if go == 'n': return('3') # Leaving L1 northwards takes you to L3
    if go == 's': return('0') # Leaving L1 southwards takes you to L0
    if go == 'e': return('3') # Leaving L1 eastwards also takes you to L3
    if go == 'w': return('2') # Leaving L1 westwards takes you to L2
    else: return 'q'

def l2():
    #print('L2')
    print("You've come to a bridge running east across a chasm")
    print("On the other side the only way to go is through a tunnel")
    print("This side of the chasm, a tunnel leads north and a path leads south")
    go = input('n e s > ')
    if go == 'n': return('1') # As per L! but applicable to L2 etc.
    if go == 'e': return('4')
    if go == 's': return('3')
    else: return 'q'

def l3():
    #print('L3')
    print("You've come to a hot and humid cavern")
    print("Tunnels run north, east and west. A path leads south.")
    print("There's a dragon here.")
    dstate = randint(1,5)
    if dstate == 1: print("The dragon seems to be asleep")
    if dstate == 2: print("The dragon looks sleepy")
    if dstate == 3: print("The dragon is awake")
    if dstate == 4: print("The dragon looks angry")
    if dstate == 5: print("The dragon is breathing fire and very angry!")
    go = input('n s e w > ')
    if go == 'n': return('1')
    if go == 's': return('2')
    if go == 'e': return('4')
    if go == 'w': return('1')
    else: return 'q'

def l4():
    #print('L4')
    print("You've arrived at a grotto. There are jewels here!")
    tstate = randint(1,4)
    if tstate > 1: print("There's a troll here wielding a cudgel")
    print("Tunnels lead east, west and south from here")
    go = input('s e w > ')
    if go == 's': return('5')
    if go == 'e': return('2')
    if go == 'w': return('3')
    else: return 'q'

def l5():
    #print('L5')
    print("The tunnel ends at a door leading to a small room")
    print("Through a grille in the door, you can see there is no way out")
    print("The only way is back, south along the tunnel")
    print("But there's gold in the room!")
    print("The door is locked.")
    go = input('s > ')
    if go == 's': return('4')
    else: return 'q'

### ********************* Main Program Start ***************************

import random
from random import randint

go = l0()   # That's call L zero (location zero), not ten!

while go != 'q':
    print()
    go = eval("l"+go+"()")  # Program always returns here to sort out where to
                            # go next. Player doesn't of course!

答案 5 :(得分:0)

已经制作了一个工作版本:http://entrian.com/goto/

注意:它是作为愚人节的笑话提供的。 (虽然工作)

digraph G {
    poly1[margin=0, width=0, height=0, shape=polygon, label="This is a polygon\nwithout skew"]
    poly2[margin=0, width=0, height=0, shape=polygon, label="This is a polygon\nwith skew", skew=0.3]
}

毋庸置疑。是的,这很有趣,但不要使用它。