`goto` en Python
compilation bytecode (6)
Debo usar goto
en Python. Encontré a los entrantes 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 soportan el código de bytes de CPython (esp. Me importa CPython y PyPy). Luego está esta pregunta relacionada , y goto
de cdjc . Y las dadas por respuestas a continuación.
Podría ir y construir el JUMP_ABSOLUTE
manualmente (es decir, escribir mi propio compilador de Python) porque existe una instrucción de este tipo ( JUMP_ABSOLUTE
y amigos). Pero me pregunto si hay una manera más fácil. ¿Es posible, a través de inspect
llamar a una sola instrucción de código de bytes? También pensé en compilar a través de Python y luego parchear automáticamente el bytecode de Python generado.
Por supuesto, la gente preguntará por qué y no me dará ninguna respuesta útil si no explico por qué realmente necesito esto. 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 de Python equivalente. Todo excepto goto
. Proyectos relacionados: PyCParser (ver interpreter.py
), PyCPython , PyLua .
Es posible que tenga el único caso de uso válido que he visto por necesitar goto
a Python. :-)
La forma más sencilla de emular goto
hacia adelante en Python es usar excepciones, ya que pueden saltar desde cualquier profundidad de las 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 complica si necesita admitir más de un destino, pero creo que se podría hacer utilizando estructuras de try/except
anidadas y varias clases de excepción, una para cada destino. Ya que C limita el goto
a una sola función, al menos no tendrá que preocuparse sobre cómo hacer que esto funcione en todas las funciones. :-) Por supuesto, no funciona para goto
inversa.
Otra cosa a tener en cuenta es que las excepciones en Python, si bien son rápidas en comparación con algunos idiomas, siguen siendo más lentas que las estructuras de control de flujo normales, como por ejemplo, while
y for
.
Esto podría ser una gran cantidad de trabajo (aunque tal vez no más de lo que ya está en), pero si pudiera generar el código de bytes de Python en lugar de la fuente de Python, no tendría ningún problema en implementar goto
, ya que el código de bytes de Python (como la mayoría de los psuedo-machine -languages) tiene un código de operación JUMP_ABSOLUTE
perfectamente JUMP_ABSOLUTE
.
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 ubicación en el juego subterráneo era un número de línea. Cuando salió de un lugar a través del túnel hacia el norte, por ejemplo, llegó a otro lugar.
La codificación fue algo así como if response == ''N'' GOTO 2400
. Así que los jugadores terminaron yendo por todos lados usando GOTOs.
Me pregunté cómo podría hacerse esto en Python y se me ocurrió esto.
Tal vez una técnica de este tipo podría usarse para otras aplicaciones donde se necesita algo como un GOTO. Si divides tu programa en partes 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 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!
Habrá algunos patrones comunes que el código que usa goto
probablemente seguirá.
En la mayoría de los casos, sospecho que todas las declaraciones de goto saltarán a una ubicación que sea más adelante y en un bloque más cerrado; Si el cuerpo de una función sigue este patrón a la perfección, transforma los goto en excepciones, con las etiquetas como bloques a excepción.
Otros casos de goto saltando de un lugar a otro en el mismo bloque, como se utilizarían en una máquina de estados. Esto probablemente se puede traducir en un bucle de envío; cada región entre una etiqueta y la siguiente se convierte en una función; los goto''s son reemplazados por next_state = ''labelname''; return
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. Todavía no tengo una respuesta para eso.
He actualizado mi decorador de Python Goto para Python 3. Puedes obtenerlo en https://github.com/cdjc/goto . Usar goto en lugar de funciones puede hacer que una máquina de estados sea 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 corregidos en la versión de python 3.
Sé lo que todos están pensando:
Sin embargo, puede haber algunos casos didácticos en los que realmente necesitas un goto
.
Esta receta de python proporciona el comando goto
como un decorador de funciones.
El decorador goto ( receta de Python por Carl Cerecke )
Esta es la receta para usted si está harto de la baja velocidad del módulo
goto
existente http://entrian.com/goto/ . Elgoto
en esta receta es aproximadamentesys.settrace
más rápido y también es más limpio (abusar desys.settrace
parece difícilmente pythonic). Debido a que este es un decorador, alerta al lector qué funciones usangoto
. No implementa el comando comefrom, aunque no es difícil extenderlo para hacerlo (ejercicio para el lector). Además, los gotos computados no son compatibles; No son pitónicos.
- Utilice
dis.dis(fn)
para mostrar el desmontaje del bytecode de una función.- Se accede a los
fn.func_code.co_code
defn.func_code.co_code
de una función mediantefn.func_code.co_code
. Esto es solo así:- La función decorada se crea exactamente igual que la anterior, pero con el bytecode actualizado para obedecer los comandos
goto
.- Esto es sólo 2.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
Actualizar
Aquí hay dos implementaciones adicionales compatibles con Python 3:
Se ha realizado una versión de trabajo: http://entrian.com/goto/ .
Nota: Se ofreció como una broma de April Fool. (trabajando aunque)
# 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"
Excusado es decir que. Sí, es gracioso, pero NO lo uses.