`goto` in Python

2019-01-31 00:44发布

I must use goto in Python. I found entrians goto but my Python implementation (CPython 2.7.1 on Mac) does not have this module, so it doesn't seem to be portable. It should at least work in all Python implementations which support CPython bytecode (esp. I care about CPython and PyPy). Then there is this related question, and cdjc's goto. And the ones given by answers below.

I could go and build up the bytecode manually (i.e. write my own Python compiler) because there is such an instruction (JUMP_ABSOLUTE and friends). But I wonder if there is an easier way. Is it possible via inspect or so to call a single bytecode instruction? I also thought about compiling via Python and then automatically patching the generated Python bytecode.


Of course, people will ask why and will not give me any helpful answer if I don't explain why I really really need this. So in short my use case: I am translating a C AST to Python AST and compiling this. I can map every logical flow (all the loops and other stuff) in some way to equivalent Python code. Everything except goto. Related projects: PyCParser (see interpreter.py), PyCPython, PyLua.

6条回答
萌系小妹纸
2楼-- · 2019-01-31 01:31

There are going to be some common patterns that code using goto is likely to follow.

In most cases, I suspect that all goto statements will jump to a location that's both later, and in a more enclosing block; If a function body follows this pattern perfectly, transform the goto's into exceptions, with the labels as the except blocks.

Other cases of goto jumping from the one place to another in the same block, as would be used in a state machine. This can probably be translated into a dispatch loop; each region between a label and the next becomes a function; goto's are replaced by next_state = 'labelname'; return

The last case, which is neither of the above and possibly non-trivial, are when the jump is into a loop body. I don't have an answer for that yet.

查看更多
Ridiculous、
3楼-- · 2019-01-31 01:34

You may have the only valid use case I have ever seen for needing goto in Python. :-)

The most straightforward way to emulate forward goto in Python is using exceptions, as these can jump out of any depth of nested control structures.

class Goto(Exception):
    pass

try:
    if foo = "bar":
        raise Goto
    print "foo is not bar"
except Goto:
    print "foo is bar"

This gets hairy if you need to support more than one destination, but I think it could be done using nested try/except structures and multiple classes of exception, one for each destination. Since C limits goto to the scope of a single function, at least you won't have to worry about how to make this work across functions. :-) Of course, it doesn't work for reverse gotos.

Another thing to note is that exceptions in Python, while fast compared to some languages, are still slower than normal flow control structures such as while and for.

This could be a lot of work (though perhaps not more than you're already in for), but if you could generate Python bytecode rather than Python source, you would have no problem implementing goto, because Python bytecode (like most psuedo-machine-languages) has a perfectly cromulent JUMP_ABSOLUTE opcode.

查看更多
Deceive 欺骗
4楼-- · 2019-01-31 01:39

I know what everybody is thinking:

xkcd GOTO

However, there might be some didactic cases where you actually need a goto.

This python recipe provides the goto command as a function decorator.

The goto decorator (Python recipe by Carl Cerecke)

This is the recipe for you if you are sick of the slow speed of the existing goto module http://entrian.com/goto/. The goto in this recipe is about 60x faster and is also cleaner (abusing sys.settrace seems hardly pythonic). Because this is a decorator, it alerts the reader which functions use goto. It does not implement the comefrom command, although it is not difficult to extend it to do so (exercise for the reader). Also, computed gotos aren't supported; they're not pythonic.

  • Use dis.dis(fn) to show the bytecode disassembly of a function.
  • The bytecodes of a function are accessed by fn.func_code.co_code. This is readonly so:
  • The decorated function is created exactly the same as the old one, but with the bytecode updated to obey the goto commands.
  • This is 2.x only; the new module is not in python 3.x (another exercise for the reader!)

Usage

@goto
def test1(n):
    s = 0

    label .myLoop

    if n <= 0:
        return s
    s += n
    n -= 1

    goto .myLoop

>>> test1(10)
55

Update

Here're two additional implementations compatible with Python 3:

查看更多
做个烂人
5楼-- · 2019-01-31 01:40

This is not exactly what you're looking for but hear me out.

Many years ago, my son and I wrote an "Adventure" game in BASIC. Each location in the underground game was a line number. When you left one location through the tunnel going north, for instance, you arrived at another location.

The coding was something like if response == 'N' GOTO 2400. So players ended up going all over the place using GOTOs.

I wondered how this might be done in Python and came up with this.

Maybe such a technique could be used for other applications where something like a GOTO is needed. If you split up your program into chunks which are functions, the following "a bit silly" coding would do the trick.

""" 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!
查看更多
孤傲高冷的网名
6楼-- · 2019-01-31 01:43

A working version has been made: http://entrian.com/goto/.

Note: It was offered as an April Fool's joke. (working though)

# Example 1: Breaking out from a deeply nested loop:
from goto import goto, label

for i in range(1, 10):
    for j in range(1, 20):
        for k in range(1, 30):
            print i, j, k
            if k == 3:
                goto .end
label .end
print "Finished\n"

Needless to say. Yes its funny, but DONT use it.

查看更多
forever°为你锁心
7楼-- · 2019-01-31 01:45

I've updated my python goto decorator for Python 3. You can get it at https://github.com/cdjc/goto. Using goto instead of functions can make a state machine about 5 times faster.

The version for python 2 is still available at http://code.activestate.com/recipes/576944-the-goto-decorator/ but it has a number of bugs that are fixed in the python 3 version.

查看更多
登录 后发表回答