'goto' en Python


Debo usar goto en Python. He encontrado entrianos goto pero mi implementación de Python (CPython 2.7.1 en Mac) no tiene este módulo, por lo que no parece ser portátil. Al menos debería funcionar en todas las implementaciones de Python que soporten CPython bytecode (esp. Me preocupo por CPython y PyPy). Luego está esta pregunta relacionada , y cdjc goto. Y los dados por las respuestas a continuación.

Podría ir y construir el bytecode manualmente (es decir, escribir mi propio Compilador Python) porque existe tal instrucción (JUMP_ABSOLUTE y amigos). Pero me pregunto si hay una manera más fácil. ¿Es posible a través de inspect o así llamar a una sola instrucción de bytecode? También pensé en compilar a través de Python y luego parchear automáticamente el bytecode Python generado.


Por supuesto, la gente preguntará por qué y no me dará ninguna respuesta útil si no explico por qué realmente necesito esto. Así que en resumen mi caso de uso: Estoy traduciendo un C AST a Python AST y compilando esto. Puedo mapear cada flujo lógico (todos los bucles y otras cosas) de alguna manera al código Python equivalente. Todo excepto goto. Proyectos relacionados: PyCParser (ver interpreter.py), PyCPython, PyLua.

Author: Albert, 2011-08-05

6 answers

Sé lo que todo el mundo está pensando:

xkcd GOTO

Sin embargo, puede haber algunos casos didácticos en los que realmente necesite un goto.

Esta receta de python proporciona el comando goto como un decorador de funciones.

El decorador goto (Python receta de Carl Cerecke )

Esta es la receta para usted si usted está enfermo de la velocidad lenta de la módulo goto existente http://entrian.com/goto / . El goto en este receta es aproximadamente 60 veces más rápido y también es más limpio (abusando sys.settrace parece apenas pitónico). Debido a que este es un decorador, alerta a la lector cuyas funciones utilizan goto. No aplica el comefrom comando, aunque no es difícil extenderlo para hacerlo (ejercicio para el lector). Además, los goto computados no son compatibles; no lo son pitónico.

  • Use dis.dis(fn) para mostrar el desmontaje de bytecode de una función.
  • Los bytecodes de una función son accessed by fn.func_code.co_code. Esto se lee solamente así:
  • La función decorada se crea exactamente igual que la antigua, pero con el bytecode actualizado para obedecer las órdenes goto.
  • Esto es 2.solo x; el nuevo módulo no está en python 3.x (otro ejercicio para el lector!)

Uso

@goto
def test1(n):
    s = 0

    label .myLoop

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

    goto .myLoop

>>> test1(10)
55

Update

Aquí hay dos implementaciones adicionales compatibles con Python 3:

 43
Author: Paolo Moretti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-12 12:49:04

Es posible que tenga el único caso de uso válido que he visto para necesitar goto en Python. :-)

La forma más sencilla de emular hacia adelante goto en Python es usar excepciones, ya que pueden saltar de cualquier profundidad de estructuras de control anidadas.

class Goto(Exception):
    pass

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

Esto se pone complicado si necesita soportar más de un destino, pero creo que podría hacerse usando estructuras anidadas try/except y múltiples clases de excepción, una para cada destino. Dado que C limita goto a la alcance de una sola función, al menos no tendrá que preocuparse de cómo hacer que esto funcione a través de las funciones. :- ) Por supuesto, no funciona para inversa gotos.

Otra cosa a tener en cuenta es que las excepciones en Python, aunque rápidas en comparación con algunos lenguajes, siguen siendo más lentas que las estructuras de control de flujo normales como while y for.

Esto podría ser mucho trabajo (aunque tal vez no más de lo que ya está en), pero si pudiera generar código de bits de Python en lugar que el código fuente de Python, no tendría ningún problema en implementar goto, porque el bytecode de Python (como la mayoría de psuedo-machine-languages) tiene un opcode JUMP_ABSOLUTE perfectamente cromulento.

 8
Author: kindall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-05 16:37:10

He actualizado mi decorador de Python goto para Python 3. Puede obtenerlo en https://github.com/cdjc/goto . Usar goto en lugar de funciones puede hacer que una máquina de estados sea aproximadamente 5 veces más rápida.

La versión para python 2 todavía está disponible en http://code.activestate.com/recipes/576944-the-goto-decorator / pero tiene una serie de errores que se corrigen en la versión de python 3.

 5
Author: cdjc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-19 20:21:02

Va a haber algunos patrones comunes que el código usando goto es probable que siga.

En la mayoría de los casos, sospecho que todas las sentencias goto saltarán a una ubicación que sea posterior, y en un bloque más envolvente; Si un cuerpo de función sigue este patrón perfectamente, transforme las goto en excepciones, con las etiquetas como los bloques except.

Otros casos de goto saltando de un lugar a otro en el mismo bloque, como se usaría en una máquina de estados. Esto puede probablemente se traduzca en un bucle de despacho; cada región entre una etiqueta y la siguiente se convierte en una función; los goto son reemplazados por next_state = 'labelname'; return

El último caso, que no es ninguno de los anteriores y posiblemente no trivial, es cuando el salto es en un cuerpo de bucle. Aún no tengo una respuesta para eso.

 1
Author: SingleNegationElimination,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-05 16:29:26

Esto no es exactamente lo que estás buscando, pero escúchame.

Hace muchos años, mi hijo y yo escribimos un juego de "Aventura" en BASIC. Cada lugar en el juego subterráneo era un número de línea. Cuando dejaste un lugar a través del túnel hacia el norte, por ejemplo, llegaste a otro lugar.

La codificación era algo así como if response == 'N' GOTO 2400. Así que los jugadores terminaron yendo por todas partes usando GOTOs.

Me pregunté cómo se podría hacer esto en Python y se me ocurrió esto.

Tal vez tal técnica podría ser utilizada para otras aplicaciones donde se necesita algo como un GOTO. Si divide su programa en trozos que son funciones, la siguiente codificación "un poco tonta" haría el truco.

""" 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!
 1
Author: John of York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-20 15:18:12

Se ha hecho una versión de trabajo: http://entrian.com/goto/.

Nota: Fue ofrecido como una broma de los Inocentes. (trabajando sin embargo)

# 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"

No hace falta decirlo. Sí, es gracioso, pero no lo uses.

 0
Author: harmv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-09 22:57:39