Est-il possible de déclarer en avant une fonction en Python?

189

Est-il possible de déclarer en avant une fonction en Python? Je veux trier une liste en utilisant ma propre cmpfonction avant qu'elle ne soit déclarée.

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

J'ai organisé mon code pour mettre la définition de cmp_configsméthode après l'invocation. Il échoue avec cette erreur:

NameError: name 'cmp_configs' is not defined

Existe-t-il un moyen de «déclarer» la cmp_configsméthode avant qu'elle ne soit utilisée? Cela rendrait mon code plus propre?

Je suppose que certaines personnes seront tentées de me dire que je devrais simplement réorganiser mon code pour ne pas avoir ce problème. Cependant, il y a des cas où cela est probablement inévitable, par exemple lors de l'implémentation de certaines formes de récursivité. Si vous n'aimez pas cet exemple, supposez que j'ai un cas dans lequel il est vraiment nécessaire de transmettre déclarer une fonction.

Considérez ce cas où la déclaration avant d'une fonction serait nécessaire en Python:

def spam():
    if end_condition():
        return end_result()
    else:
        return eggs()

def eggs():
    if end_condition():
        return end_result()
    else:
        return spam()

end_conditionet end_resultont été définis précédemment.

La seule solution est-elle de réorganiser le code et de toujours mettre les définitions avant les appels?

Nathan Fellman
la source

Réponses:

76

Si vous ne voulez pas définir une fonction avant qu'elle ne soit utilisée, et la définir après est impossible, qu'en est-il de la définir dans un autre module?

Techniquement, vous le définissez toujours en premier, mais c'est propre.

Vous pouvez créer une récursivité comme celle-ci:

def foo():
    bar()

def bar():
    foo()

Les fonctions de Python sont anonymes, tout comme les valeurs sont anonymes, mais elles peuvent être liées à un nom.

Dans le code ci-dessus, foo()n'appelle pas une fonction avec le nom foo, il appelle une fonction qui se trouve être liée au nom fooau moment où l'appel est effectué. Il est possible de redéfinir fooailleurs, barpuis d'appeler la nouvelle fonction.

Votre problème ne peut pas être résolu car c'est comme demander à obtenir une variable qui n'a pas été déclarée.

RichN
la source
47
en bref, si vous avez si __name__ == '__main__': main () comme dernière ligne de votre script, tout ira bien!
Filipe Pina
3
@FilipePina Je n'ai pas compris votre commentaire - pourquoi ne pouvez-vous pas mettre la dernière ligne du code aussi simplement main()?
Sanjay Manohar
11
@SanjayManohar: pour éviter de l'exécuter surimport your_module
jfs
2
Je voudrais ajouter - il est parfois possible de contourner ces problèmes en utilisant des lambdas puisqu'ils sont évalués plus tard.
Joe le
2
Wrt "anonyme", vous voulez vraiment dire "objets de première classe".
danielm
119

Ce que vous pouvez faire est d'encapsuler l'appel dans une fonction qui lui est propre.

Pour que

foo()

def foo():
    print "Hi!"

va casser, mais

def bar():
    foo()

def foo():
    print "Hi!"

bar()

fonctionnera correctement.

La règle générale Pythonn'est pas que la fonction doit être définie plus haut dans le code (comme dansPascal ), mais qu'elle doit être définie avant son utilisation.

J'espère que cela pourra aider.

Vanya
la source
20
+1 réponse la plus directe, avec le concept clé de voûte: Pascal = définir plus haut, Python = définir plus tôt.
Bob Stein
1
C'est la bonne réponse, cela explique également pourquoi la if __name__=="__main__":solution fonctionne.
00prometheus
2
Si je comprends bien, cela signifie que l'exemple de spam () et d'œufs () d'OP est bien tel qu'il est écrit. Est-ce exact?
krubo le
1
@krubo oui, c'est bien comme écrit
lxop
92

Si vous démarrez votre script de la manière suivante:

if __name__=="__main__":
   main()

alors vous n'avez probablement pas à vous soucier de choses comme la "déclaration anticipée". Vous voyez, l'interpréteur allait charger toutes vos fonctions et ensuite démarrer votre fonction main (). Bien sûr, assurez-vous que toutes les importations sont correctes aussi ;-)

À bien y penser, je n'ai jamais entendu une telle chose comme "déclaration avant" en python ... mais là encore, je me trompe peut-être ;-)

jldupont
la source
14
+1 réponse la plus pratique: si vous placez ce code au bas du fichier source le plus externe, vous êtes libre de le définir dans n'importe quel ordre.
Bob Stein du
2
Excellent conseil; m'aide vraiment; car je préfère beaucoup plus la programmation «descendante» que la programmation ascendante.
GhostCat
10

Si l'appel à cmp_configs se trouve dans sa propre définition de fonction, tout devrait bien se passer. Je vais donner un exemple.

def a():
  b()  # b() hasn't been defined yet, but that's fine because at this point, we're not
       # actually calling it. We're just defining what should happen when a() is called.

a()  # This call fails, because b() hasn't been defined yet, 
     # and thus trying to run a() fails.

def b():
  print "hi"

a()  # This call succeeds because everything has been defined.

En général, mettre votre code dans des fonctions (comme main ()) résoudra votre problème; appelez simplement main () à la fin du fichier.

BJ Homer
la source
10

Je m'excuse d'avoir relancé ce fil, mais il y avait une stratégie non discutée ici qui peut être applicable.

En utilisant la réflexion, il est possible de faire quelque chose qui s'apparente à une déclaration en avant. Par exemple, disons que vous avez une section de code qui ressemble à ceci:

# We want to call a function called 'foo', but it hasn't been defined yet.
function_name = 'foo'
# Calling at this point would produce an error

# Here is the definition
def foo():
    bar()

# Note that at this point the function is defined
    # Time for some reflection...
globals()[function_name]()

Ainsi, nous avons déterminé quelle fonction nous voulons appeler avant qu'elle ne soit réellement définie, en fait une déclaration forward. En python, l'instruction globals()[function_name]()est la même que foo()si function_name = 'foo'pour les raisons évoquées ci-dessus, car python doit rechercher chaque fonction avant de l'appeler. Si l'on utilisait letimeit module pour voir comment ces deux déclarations se comparent, elles ont exactement le même coût de calcul.

Bien sûr, l'exemple ici est très inutile, mais si l'on devait avoir une structure complexe qui nécessitait d'exécuter une fonction, mais qui devait être déclarée avant (ou structurellement cela n'a guère de sens de l'avoir après), on peut simplement stocker une chaîne et essayez d'appeler la fonction plus tard.

KGardevoir
la source
9

Il n'y a rien de tel en python que la déclaration forward. Vous devez juste vous assurer que votre fonction est déclarée avant qu'elle ne soit nécessaire. Notez que le corps d'une fonction n'est pas interprété tant que la fonction n'est pas exécutée.

Prenons l'exemple suivant:

def a():
   b() # won't be resolved until a is invoked.

def b(): 
   print "hello"

a() # here b is already defined so this line won't fail.

Vous pouvez penser qu'un corps d'une fonction n'est qu'un autre script qui sera interprété une fois que vous appelez la fonction.

Piotr Czapla
la source
7

Non, je ne pense pas qu'il existe un moyen de déclarer en avant une fonction en Python.

Imaginez que vous êtes l'interpréteur Python. Quand tu arrives à la ligne

print "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

soit vous savez ce qu'est cmp_configs, soit vous ne le savez pas. Pour continuer, vous devez connaître cmp_configs. Peu importe s'il y a récursivité.

unutbu
la source
9
Eh bien, c'est si vous ne faites qu'une seule passe sur le code. Certains compilateurs (et je réalise que python est interprété) font deux passes, afin que ces choses puissent être comprises. une déclaration anticipée, ou au moins une sorte de découverte de portée, serait vraiment bien.
Mark Lakewood
7

Parfois, un algorithme est plus facile à comprendre de haut en bas, en commençant par la structure globale et en explorant les détails.

Vous pouvez le faire sans déclaration préalable:

def main():
  make_omelet()
  eat()

def make_omelet():
  break_eggs()
  whisk()
  fry()

def break_eggs():
  for egg in carton:
    break(egg)

# ...

main()
funroll
la source
4
# declare a fake function (prototype) with no body
def foo(): pass

def bar():
    # use the prototype however you see fit
    print(foo(), "world!")

# define the actual function (overwriting the prototype)
def foo():
    return "Hello,"

bar()

Production:

Hello, world!
jmurphy61
la source
3

Vous ne pouvez pas déclarer en avant une fonction en Python. Si la logique s'exécute avant d'avoir défini des fonctions, vous avez probablement un problème de toute façon. Mettez votre action dans unif __name__ == '__main__' à la fin de votre script (en exécutant une fonction que vous nommez "main" si ce n'est pas trivial) et votre code sera plus modulaire et vous pourrez l'utiliser comme module si jamais vous en avez besoin à.

Remplacez également cette compréhension de liste par un générateur express (ie, print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))

N'utilisez pas non plus cmp, ce qui est obsolète. Utilisez keyet fournissez une fonction inférieure à.

Mike Graham
la source
Comment fournir une fonction moins que?
Nathan Fellman
Au lieu de cmp_configs, vous définiriez une fonction qui prend deux arguments et renvoie True si le premier est inférieur au second et False sinon.
Mike Graham
Pour ceux d'entre nous qui viennent d'un arrière-plan de type C, il n'y a rien de déraisonnable dans l'exécution de la logique avant que les fonctions ne soient définies. Pensez: "compilateur multi-passes". Parfois, il faut un certain temps pour s'adapter aux nouvelles langues :)
Luke H
3

Importez le fichier lui-même. En supposant que le fichier s'appelle test.py:

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')
user10488833
la source
1

"réorganisez simplement mon code pour ne pas avoir ce problème." Correct. Facile à faire. Fonctionne toujours.

Vous pouvez toujours fournir la fonction avant sa référence.

"Cependant, il y a des cas où cela est probablement inévitable, par exemple lors de la mise en œuvre de certaines formes de récursivité"

Je ne vois pas comment c'est possible, même à distance. Veuillez fournir un exemple d'endroit où vous ne pouvez pas définir la fonction avant son utilisation.

S.Lott
la source
J'ai une telle situation. J'essaie de passer des types dans un décorateur de fonctions, et les types sont définis plus bas dans le module. Je ne peux pas déplacer les types incriminés, car cela briserait la chaîne d'héritage.
Joe le
Je l'ai corrigé en passant un lambda à mon décorateur au lieu du type réel; mais je ne saurais pas comment y remédier autrement (cela ne m'obligerait pas à réorganiser mes héritages)
Joe
0

Maintenant, attendez une minute. Lorsque votre module atteint l'instruction d'impression dans votre exemple, avant qu'elle cmp_configsn'ait été définie, que voulez-vous qu'il fasse exactement?

Si votre publication d'une question à l'aide de l'impression tente vraiment de représenter quelque chose comme ceci:

fn = lambda mylist:"\n".join([str(bla)
                         for bla in sorted(mylist, cmp = cmp_configs)])

alors il n'y a aucune obligation de définir cmp_configs avant d'exécuter cette instruction, il suffit de la définir plus tard dans le code et tout ira bien.

Maintenant, si vous essayez de référencer cmp_configscomme valeur par défaut d'un argument au lambda, alors c'est une autre histoire:

fn = lambda mylist,cmp_configs=cmp_configs : \
    "\n".join([str(bla) for bla in sorted(mylist, cmp = cmp_configs)])

Vous avez maintenant besoin d'une cmp_configsvariable définie avant d'atteindre cette ligne.

[EDIT - cette partie suivante s'avère incorrecte, car la valeur de l'argument par défaut sera attribuée lorsque la fonction est compilée, et cette valeur sera utilisée même si vous modifiez la valeur de cmp_configs plus tard.]

Heureusement, Python étant si adaptable au type qu'il est, ne se soucie pas de ce que vous définissez cmp_configs, vous pouvez donc simplement précéder cette déclaration:

cmp_configs = None

Et le compilateur sera content. Assurez-vous simplement de déclarer le réel cmp_configsavant de l'invoquer fn.

PaulMcG
la source
-1

Une façon consiste à créer une fonction de gestionnaire. Définissez le gestionnaire dès le début et placez le gestionnaire sous toutes les méthodes que vous devez appeler.

Ensuite, lorsque vous appelez la méthode du gestionnaire pour appeler vos fonctions, elles seront toujours disponibles.

Le gestionnaire peut accepter un argument nameOfMethodToCall. Puis utilise un tas d'instructions if pour appeler la bonne méthode.

Cela résoudrait votre problème.

def foo():
    print("foo")
    #take input
    nextAction=input('What would you like to do next?:')
    return nextAction

def bar():
    print("bar")
    nextAction=input('What would you like to do next?:')
    return nextAction

def handler(action):
    if(action=="foo"):
        nextAction = foo()
    elif(action=="bar"):
        nextAction = bar()
    else:
        print("You entered invalid input, defaulting to bar")
        nextAction = "bar"
    return nextAction

nextAction=input('What would you like to do next?:')

while 1:
    nextAction = handler(nextAction)
obesechicken13
la source
cela semble très impythonique. Python devrait gérer ce genre de choses tout seul.
Nathan Fellman
relisez la réponse acceptée. Python n'a pas besoin que la fonction soit définie jusqu'à ce que vous l' appeliez , pas seulement de l'utiliser dans une définition.
tacaswell
-3

Oui, nous pouvons vérifier cela.

Contribution

print_lyrics() 
def print_lyrics():

    print("I'm a lumberjack, and I'm okay.")
    print("I sleep all night and I work all day.")

def repeat_lyrics():
    print_lyrics()
    print_lyrics()
repeat_lyrics()

Production

I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Comme BJ Homer l'a mentionné dans les commentaires ci-dessus, une règle générale en Python n'est pas que la fonction doit être définie plus haut dans le code (comme en Pascal), mais qu'elle doit être définie avant son utilisation.

J'espère que cela pourra aider.

Satish Reddy Venkannagari
la source
2
N'est-il pas print_lyrics()appelé (utilisé) à la ligne 1 avant qu'il ne soit défini? J'ai copié ce morceau de code et essayé de l'exécuter, et cela me donne l'erreur NameError: name 'print_lyrics' is not definedsur la ligne 1. Pouvez-vous expliquer cela?
Bugs Buggy