web-dev-qa-db-fra.com

`goto` en Python

Je dois utiliser goto en Python. J'ai trouvé entrians goto mais mon implémentation Python (CPython 2.7.1 sur Mac) ne possède pas ce module, il ne semble donc pas être portable. Cela devrait au moins fonctionner dans toutes les implémentations Python qui supportent le bytecode CPython (en particulier, CPython et PyPy me tiennent à cœur). Ensuite, il y a cette question connexe , et la variable goto de cdjc . Et ceux donnés par les réponses ci-dessous.

Je pourrais aller construire le bytecode manuellement (c’est-à-dire écrire mon propre compilateur Python) car il existe une telle instruction (JUMP_ABSOLUTE et amis). Mais je me demande s'il existe un moyen plus facile. Est-il possible via inspect ou alors d'appeler une seule instruction bytecode? J'ai aussi pensé à compiler via Python puis à patcher automatiquement le bytecode Python généré.


Bien sûr, les gens vont demander pourquoi et ne me donneront aucune réponse utile si je n'explique pas pourquoi j'ai vraiment besoin de ça. En bref, mon cas d'utilisation: je traduis un C AST en Python AST et le compile. Je peux mapper chaque flux logique (toutes les boucles et autres éléments) d'une manière ou d'une autre avec du code Python équivalent. Tout sauf goto. Projets associés: PyCParser (voir interpreter.py ), PyCPython , PyLua .

28
Albert

Je sais ce que tout le monde pense:

 xkcd GOTO

Cependant, il peut y avoir des cas didactiques pour lesquels vous avez réellement besoin d'une goto.

Cette recette python fournit la commande goto en tant que décorateur de fonctions.

Le décorateur de goto (recette Python de Carl Cerecke)

C’est la recette pour vous si vous en avez marre de la faible vitesse du module Existant gotohttp://entrian.com/goto/ . La goto de cette recette est environ 60 fois plus rapide et plus propre (abuser de sys.settrace Semble à peine pythonique). S'agissant d'un décorateur, il avertit le lecteur Dont les fonctions utilisent goto. Il ne met pas en œuvre la commande comefrom , Bien qu'il ne soit pas difficile de l'étendre (exercice Pour le lecteur). De plus, les gotos calculés ne sont pas supportés; ils ne sont pas Pythonic.

  • Utilisez dis.dis(fn) pour afficher le désassemblage en bytecode d’une fonction.
  • Les bytecodes d'une fonction sont accessibles via fn.func_code.co_code. Ceci est en lecture seule donc:
  • La fonction décorée est créée exactement de la même façon que l'ancienne, , Mais avec le code intermédiaire mis à jour pour obéir aux commandes goto.
  • Ceci est seulement 2.x; le nouveau module n'est pas en python 3.x (un autre exercice pour le lecteur!)

Utilisation

@goto
def test1(n):
    s = 0

    label .myLoop

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

    goto .myLoop

>>> test1(10)
55

Mise à jour

Voici deux implémentations supplémentaires compatibles avec Python 3:

43
Paolo Moretti

Vous pouvez avoir le seul cas d'utilisation valide que j'ai jamais vu pour avoir besoin de goto en Python. :-) 

Le moyen le plus direct d'émuler une variable goto dans Python consiste à utiliser des exceptions, car elles peuvent sortir de n'importe quelle profondeur de structures de contrôle imbriquées.

class Goto(Exception):
    pass

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

Cela devient complexe si vous devez prendre en charge plusieurs destinations, mais je pense que cela pourrait être fait à l'aide de structures imbriquées try/except et de plusieurs classes d'exceptions, une pour chaque destination. Etant donné que C limite goto à la portée d’une seule fonction, au moins vous n’aurez plus à vous soucier de la façon dont cela fonctionnera à travers . :-) Bien sûr, cela ne marche pas pour les gotos inverses.

Une autre chose à noter est que les exceptions en Python, bien que rapides comparées à certaines langues, sont toujours plus lentes que les structures de contrôle de flux normales telles que while et for.

Cela pourrait prendre beaucoup de travail (mais peut-être pas plus que ce que vous aviez déjà prévu), mais si vous pouviez générer du bytecode Python plutôt que du code source Python, vous n'auriez aucun problème à implémenter goto, car le bytecode Python (comme la plupart des logiciels psuedo -languages) possède un opcode JUMP_ABSOLUTE parfaitement conforme.

8
kindall

J'ai mis à jour mon python goto decorator pour Python 3. Vous pouvez l'obtenir à l'adresse suivante: https://github.com/cdjc/goto . Utiliser goto au lieu de fonctions peut rendre une machine d'état environ 5 fois plus rapide.

La version pour Python 2 est toujours disponible sur http://code.activestate.com/recipes/576944-the-goto-decorator/ mais elle contient un certain nombre de bogues corrigés dans la version 3 de Python.

5
cdjc

Il y aura des modèles courants que le code utilisant goto est susceptible de suivre. 

Dans la plupart des cas, je soupçonne que toutes les déclarations goto iront à un emplacement situé à la fois plus tard et dans un bloc plus fermé; Si un corps de fonction suit parfaitement ce modèle, transformez la goto en exceptions, avec les étiquettes comme blocs d'exception.

Autres cas de sauts d'un endroit à un autre dans le même bloc, comme cela serait utilisé dans une machine à états. Cela peut probablement être traduit en boucle de répartition; chaque région entre une étiquette et la suivante devient une fonction; les goto sont remplacés par next_state = 'labelname'; return

Le dernier cas, qui n'est ni l'un ni l'autre et peut-être pas trivial, est celui où le saut est dans un corps de boucle. Je n'ai pas encore de réponse à cela.

Ce n'est pas exactement ce que vous cherchez, mais écoutez-moi. 

Il y a de nombreuses années, mon fils et moi avons écrit un jeu "Adventure" en BASIC. Chaque emplacement dans le jeu souterrain était un numéro de ligne. Lorsque vous avez quitté un endroit par le tunnel en direction du nord, par exemple, vous êtes arrivé à un autre endroit. 

Le codage était quelque chose comme if response == 'N' GOTO 2400. Les joueurs ont donc fini par utiliser GOTO.

Je me suis demandé comment cela pourrait être fait en Python et je l'ai trouvé. 

Peut-être qu'une telle technique pourrait-elle être utilisée pour d'autres applications nécessitant quelque chose comme un GOTO. Si vous divisez votre programme en morceaux qui sont des fonctions, le codage suivant "un peu ridicule" ferait l'affaire.

""" 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
John of York

Une version de travail a été faite: http://entrian.com/goto/ .

Remarque: Il s'agissait d'une blague du poisson d'avril. (travaille bien)

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

Il va sans dire. Oui c'est drôle, mais ne l'utilisez pas.

0
harmv