¿Es posible declarar hacia adelante una función en Python?

189

¿Es posible declarar hacia adelante una función en Python? Quiero ordenar una lista usando mi propia cmpfunción antes de declararla.

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

He organizado mi código para poner la definición del cmp_configsmétodo después de la invocación. Falla con este error:

NameError: name 'cmp_configs' is not defined

¿Hay alguna forma de "declarar" el cmp_configsmétodo antes de usarlo? ¿Haría que mi código se vea más limpio?

Supongo que algunas personas se verán tentadas a decirme que debería reorganizar mi código para no tener este problema. Sin embargo, hay casos en que esto es inevitable, por ejemplo, cuando se implementan algunas formas de recursión. Si no le gusta este ejemplo, suponga que tengo un caso en el que es realmente necesario reenviar la declaración de una función.

Considere este caso donde sería necesario declarar hacia adelante una función en Python:

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

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

Dónde end_conditiony end_resultse han definido previamente.

¿Es la única solución para reorganizar el código y siempre poner definiciones antes de las invocaciones?

Nathan Fellman
fuente

Respuestas:

76

Si no desea definir una función antes de usarla, y definirla después es imposible, ¿qué hay de definirla en algún otro módulo?

Técnicamente, todavía lo define primero, pero está limpio.

Puede crear una recursión como la siguiente:

def foo():
    bar()

def bar():
    foo()

Las funciones de Python son anónimas al igual que los valores son anónimos, pero pueden estar vinculados a un nombre.

En el código anterior, foo()no llama a una función con el nombre foo, llama a una función que está vinculada al nombre fooen el momento en que se realiza la llamada. Es posible redefinir fooen otro lugar y barluego llamar a la nueva función.

Su problema no puede resolverse porque es como pedir una variable que no ha sido declarada.

RichN
fuente
47
en resumen, si tiene si __name__ == '__main__': main () como la última línea en su script, ¡todo estará bien!
Filipe Pina
3
@FilipePina No he entendido tu comentario, ¿por qué no puedes poner la última línea del código simplemente main()?
Sanjay Manohar
11
@SanjayManohar: para evitar ejecutarlo enimport your_module
jfs
2
Me gustaría agregar: a veces es posible evitar estos problemas usando lambdas, ya que se evalúan más adelante.
Joe
2
Wrt "anónimo", realmente quiere decir "objetos de primera clase".
danielm
119

Lo que puede hacer es envolver la invocación en una función propia.

Así que eso

foo()

def foo():
    print "Hi!"

se romperá, pero

def bar():
    foo()

def foo():
    print "Hi!"

bar()

funcionará correctamente

La regla general en noPython es que la función deba definirse más arriba en el código (como en ), sino que debe definirse antes de su uso.Pascal

Espero que ayude.

Vanya
fuente
20
+1 respuesta más directa, con el concepto clave: Pascal = definir más alto, Python = definir antes.
Bob Stein
1
Esta es la respuesta correcta, también explica por qué funciona la if __name__=="__main__":solución.
00prometheus
2
Si entiendo correctamente, significa que el ejemplo de spam () y eggs () de OP está bien como está escrito. ¿Es eso correcto?
Krubo
1
@krubo sí, está bien como está escrito
lxop
92

Si inicia su secuencia de comandos a través de lo siguiente:

if __name__=="__main__":
   main()

entonces probablemente no tenga que preocuparse por cosas como la "declaración de reenvío". Verá, el intérprete cargaría todas sus funciones y luego iniciaría su función main (). Por supuesto, asegúrese de tener todas las importaciones correctas también ;-)

Ahora que lo pienso, nunca he escuchado algo así como "declaración directa" en Python ... pero, de nuevo, podría estar equivocado ;-)

jldupont
fuente
14
+1 respuesta más práctica: si coloca este código en la parte inferior del archivo fuente más externo, puede definirlo en cualquier orden.
Bob Stein
2
Gran consejo; realmente me ayuda; ya que prefiero mucho más la programación "de arriba hacia abajo" que la de abajo hacia arriba.
GhostCat
10

Si la llamada a cmp_configs está dentro de su propia definición de función, debería estar bien. Daré un ejemplo.

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 general, poner su código dentro de las funciones (como main ()) resolverá su problema; simplemente llame a main () al final del archivo.

BJ Homer
fuente
10

Pido disculpas por revivir este hilo, pero hubo una estrategia no discutida aquí que puede ser aplicable.

Usando la reflexión, es posible hacer algo similar a la declaración directa. Por ejemplo, supongamos que tiene una sección de código que se ve así:

# 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]()

Entonces, de esta manera, hemos determinado a qué función queremos llamar antes de que se defina realmente, efectivamente una declaración directa. En python, la declaración globals()[function_name]()es la misma que foo()si fuera function_name = 'foo'por las razones discutidas anteriormente, ya que python debe buscar cada función antes de llamarla. Si uno usara el timeitmódulo para ver cómo se comparan estas dos afirmaciones, tienen exactamente el mismo costo computacional.

Por supuesto, el ejemplo aquí es muy inútil, pero si uno tuviera una estructura compleja que necesitara ejecutar una función, pero debe declararse antes (o estructuralmente tiene poco sentido tenerla después), uno puede almacenar una cadena y intente llamar a la función más tarde.

KGardevoir
fuente
9

No hay tal cosa en Python como la declaración directa. Solo tiene que asegurarse de que su función se declare antes de que sea necesaria. Tenga en cuenta que el cuerpo de una función no se interpreta hasta que se ejecuta la función.

Considere el siguiente ejemplo:

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.

Puede pensar que el cuerpo de una función es solo otro script que se interpretará una vez que llame a la función.

Piotr Czapla
fuente
7

No, no creo que haya ninguna manera de declarar hacia adelante una función en Python.

Imagina que eres el intérprete de Python. Cuando llegas a la línea

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

o sabes lo que es cmp_configs o no. Para continuar, debe conocer cmp_configs. No importa si hay recurrencia.

unutbu
fuente
9
bueno, esto es si solo estás haciendo una pasada sobre el código. Algunos compiladores (y me doy cuenta de que Python está interpretado) hacen dos pases, para que estas cosas se puedan resolver. La declaración directa, o al menos algún tipo de descubrimiento de alcance, sería realmente bueno.
Mark Lakewood
7

A veces, un algoritmo es más fácil de entender de arriba a abajo, comenzando con la estructura general y profundizando en los detalles.

Puede hacerlo sin declaraciones a futuro:

def main():
  make_omelet()
  eat()

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

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

# ...

main()
funroll
fuente
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()

Salida:

Hello, world!
jmurphy61
fuente
3

No puede declarar hacia adelante una función en Python. Si tiene una ejecución lógica antes de haber definido las funciones, de todos modos probablemente tenga un problema. Ponga su acción if __name__ == '__main__'al final de su secuencia de comandos (ejecutando una función que nombre "principal" si no es trivial) y su código será más modular y podrá usarlo como módulo si alguna vez necesita a.

Además, reemplace esa comprensión de la lista con un generador express (es decir, print "\n".join(str(bla) for bla in sorted(mylist, cmp=cmp_configs)))

Además, no use cmp, que está en desuso. Use keyy proporcione una función menor que.

Mike Graham
fuente
¿Cómo proporciono una función menor que?
Nathan Fellman
En lugar de cmp_configs, definiría una función que toma dos argumentos y devuelve True si el primero es menor que el segundo y False en caso contrario.
Mike Graham el
Para aquellos de nosotros que venimos de un entorno tipo C, no hay nada irrazonable en la ejecución lógica antes de definir las funciones. Piensa: "compilador de varios pasos". A veces lleva un tiempo adaptarse a los nuevos idiomas :)
Luke H
3

Importa el archivo en sí. Suponiendo que el archivo se llama test.py:

import test

if __name__=='__main__':
    test.func()
else:
    def func():
        print('Func worked')
usuario10488833
fuente
1

"simplemente reorganice mi código para que no tenga este problema". Correcto. Fácil de hacer. Siempre funciona

Siempre puede proporcionar la función antes de su referencia.

"Sin embargo, hay casos en los que esto es inevitable, por ejemplo, cuando se implementan algunas formas de recursión".

No puedo ver cómo eso es remotamente posible. Proporcione un ejemplo de un lugar donde no pueda definir la función antes de usarla.

S.Lott
fuente
Tengo una situación así. Estoy tratando de pasar tipos en un decorador de funciones, y los tipos se definen más abajo en el módulo. No puedo mover los tipos ofensivos hacia arriba, porque eso rompería la cadena de herencia.
Joe
Lo arreglé pasando una lambda a mi decorador en lugar del tipo real; pero no sabría cómo solucionarlo de otra manera (eso no me obligaría a reorganizar mis herencias)
Joe
0

Ahora espera un minuto. Cuando su módulo alcanza la declaración de impresión en su ejemplo, antes de que cmp_configsse haya definido, ¿qué es exactamente lo que espera que haga?

Si su publicación de una pregunta usando print realmente está tratando de representar algo como esto:

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

entonces no hay ningún requisito para definir cmp_configsantes de ejecutar esta declaración, simplemente defínalo más adelante en el código y todo estará bien.

Ahora, si está intentando hacer referencia cmp_configscomo valor predeterminado de un argumento a la lambda, esta es una historia diferente:

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

Ahora necesita una cmp_configsvariable definida antes de llegar a esta línea.

[EDITAR - esta siguiente parte resulta no ser correcta, ya que el valor de argumento predeterminado se asignará cuando se compila la función, y ese valor se usará incluso si cambia el valor de cmp_configs más tarde].

Afortunadamente, a Python, que es tan adaptable a los tipos, no le importa lo que defina cmp_configs, por lo que podría prefacio con esta declaración:

cmp_configs = None

Y el compilador será feliz. Solo asegúrese de declarar lo real cmp_configsantes de invocar fn.

PaulMcG
fuente
-1

Una forma es crear una función de controlador. Defina el controlador desde el principio y coloque el controlador debajo de todos los métodos que necesita llamar.

Luego, cuando invoque el método del controlador para llamar a sus funciones, siempre estarán disponibles.

El manejador podría tomar una discusión nameOfMethodToCall. Luego usa un montón de sentencias if para llamar al método correcto.

Esto resolvería tu problema.

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)
obeso
fuente
eso parece muy poco pitónico. Python debería manejar este tipo de cosas por sí solo.
Nathan Fellman
vuelva a leer la respuesta aceptada. Python no necesita que se defina la función hasta que la llame , no solo la usa en una definición.
tacaswell
-3

Sí, podemos verificar esto.

Entrada

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()

Salida

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.

Como BJ Homer mencionó en los comentarios anteriores, una regla general en Python no es que la función deba definirse más arriba en el código (como en Pascal), sino que debe definirse antes de su uso.

Espero que ayude.

Satish Reddy Venkannagari
fuente
2
¿No se print_lyrics()llama (usa) en la línea 1 antes de que se defina? Copié este código e intenté ejecutarlo, y me da el error NameError: name 'print_lyrics' is not defineden la línea 1. ¿Puede explicar esto?
Bugs Buggy