Forma eficiente de ejecutar una función solo una vez en un bucle

En este momento, estoy haciendo cosas como las siguientes, que se están volviendo tediosas:

run_once = 0 while 1: if run_once == 0: myFunction() run_once = 1: 

¿Supongo que hay una forma más aceptada de manejar estas cosas?

Lo que busco es tener una función ejecutada una vez, a pedido. Por ejemplo, al presionar un botón determinado. Es una aplicación interactiva que tiene muchos conmutadores controlados por el usuario. Tener una variable basura para cada conmutador, solo para mantener un registro de si se ha ejecutado o no, parece algo ineficiente.

Usaría un decorador en la función para manejar el seguimiento de cuántas veces se ejecuta.

 def run_once(f): def wrapper(*args, **kwargs): if not wrapper.has_run: wrapper.has_run = True return f(*args, **kwargs) wrapper.has_run = False return wrapper @run_once def my_function(foo, bar): return foo+bar 

Ahora my_function solo se ejecutará una vez. Otras llamadas a él devolverán None . Solo agregue una cláusula else a if si desea que devuelva algo más. De tu ejemplo, no necesita devolver nada nunca.

Si no controla la creación de la función, o si la función debe usarse normalmente en otros contextos, también puede aplicar el decorador de forma manual.

 action = run_once(my_function) while 1: if predicate: action() 

Esto dejará my_function disponible para otros usos.

Por último, si solo necesita ejecutarlo una vez dos veces, simplemente puede hacer

 action = run_once(my_function) action() # run once the first time action.has_run = False action() # run once the second time 

Otra opción es configurar el objeto de código func_code para que su función sea un objeto de código para una función que no hace nada. Esto debe hacerse al final de su cuerpo de función.

Por ejemplo:

 def run_once(): # Code for something you only want to execute once run_once.func_code = (lambda:None).func_code 

Aquí run_once.func_code = (lambda:None).func_code reemplaza el código ejecutable de su función con el código de lambda: Ninguna, por lo que todas las llamadas posteriores a run_once() no harán nada.

Esta técnica es menos flexible que el enfoque de decorador sugerido en la respuesta aceptada , pero puede ser más conciso si solo tiene una función que desea ejecutar una vez.

Ejecutar la función antes del bucle. Ejemplo:

 myFunction() while True: # all the other code being executed in your loop 

Esta es la solución obvia. Si hay más de lo que parece, la solución puede ser un poco más complicada.

Supongo que esta es una acción que desea que se realice una vez, si se cumplen algunas condiciones. Como no siempre realizarás la acción, no puedes hacerlo incondicionalmente fuera del bucle. Algo así como la recuperación perezosa de algunos datos (y su almacenamiento en caché) si recibe una solicitud, pero no la recupera de otra manera.

 def do_something(): [x() for x in expensive_operations] global action action = lambda : None action = do_something while True: # some sort of complex logic... if foo: action() 

Hay muchas maneras de hacer lo que quieres; sin embargo, tenga en cuenta que es muy posible que, como se describe en la pregunta, no tenga que llamar a la función dentro del bucle.

Si insiste en tener la llamada de función dentro del bucle, también puede hacer:

 needs_to_run= expensive_function while 1: … if needs_to_run: needs_to_run(); needs_to_run= None … 

He pensado en otra forma, ligeramente inusual, pero muy efectiva, de hacer esto que no requiera funciones o clases de decoración. En su lugar, solo utiliza un argumento de palabra clave mutable, que debería funcionar en la mayoría de las versiones de Python. La mayoría de las veces, esto es algo que se debe evitar, ya que normalmente no querría que un valor de argumento predeterminado cambie de una llamada a otra, pero esa capacidad puede aprovecharse en este caso y usarse como un mecanismo de almacenamiento barato. Así es como funcionaría:

 def my_function1(_has_run=[]): if _has_run: return print("my_function1 doing stuff") _has_run.append(1) def my_function2(_has_run=[]): if _has_run: return print("my_function2 doing some other stuff") _has_run.append(1) for i in range(10): my_function1() my_function2() print('----') my_function1(_has_run=[]) # Force it to run. 

Salida:

 my_function1 doing stuff my_function2 doing some other stuff ---- my_function1 doing stuff 

Esto podría simplificarse un poco más haciendo lo que @gnibbler sugirió en su respuesta y usando un iterador (que se introdujo en Python 2.2):

 from itertools import count def my_function3(_count=count()): if next(_count): return print("my_function3 doing something") for i in range(10): my_function3() print('----') my_function3(_count=count()) # Force it to run. 

Salida:

 my_function3 doing something ---- my_function3 doing something 

Aquí hay una respuesta que no implica la reasignación de funciones, pero aún así evita la necesidad de que el feo control “es lo primero”.

Python 2.5 y posteriores admiten __missing__ .

 def do_once_varname1(): print 'performing varname1' return 'only done once for varname1' def do_once_varname2(): print 'performing varname2' return 'only done once for varname2' class cdict(dict): def __missing__(self,key): val=self['do_once_'+key]() self[key]=val return val cache_dict=cdict(do_once_varname1=do_once_varname1,do_once_varname2=do_once_varname2) if __name__=='__main__': print cache_dict['varname1'] # causes 2 prints print cache_dict['varname2'] # causes 2 prints print cache_dict['varname1'] # just 1 print print cache_dict['varname2'] # just 1 print 

Salida:

 performing varname1 only done once for varname1 performing varname2 only done once for varname2 only done once for varname1 only done once for varname2 

Suponiendo que hay alguna razón por la que no se puede llamar a myFunction() antes del bucle

 from itertools import count for i in count(): if i==0: myFunction() 

Aquí hay una forma explícita de codificar esto, donde el estado de las funciones que se han llamado se mantiene localmente (por lo que se evita el estado global). No me gustan mucho los formularios no explícitos sugeridos en otras respuestas: es demasiado sorprendente ver f () y esto no significa que se llame a f ().

Esto funciona mediante el uso de dict.pop, que busca una clave en un dict, elimina la clave del dict y toma un valor predeterminado para usar en caso de que no se encuentre la clave.

 def do_nothing(*args, *kwargs): pass # A list of all the functions you want to run just once. actions = [ my_function, other_function ] actions = dict((action, action) for action in actions) while True: if some_condition: actions.pop(my_function, do_nothing)() if some_other_condition: actions.pop(other_function, do_nothing)() 

¿Por qué es esto diferente de su código?

 myFunction() while 1: # rest of your code pass 

Si entiendo la pregunta actualizada correctamente, algo como esto debería funcionar

 def function1(): print "function1 called" def function2(): print "function2 called" def function3(): print "function3 called" called_functions = set() while True: n = raw_input("choose a function: 1,2 or 3 ") func = {"1": function1, "2": function2, "3": function3}.get(n) if func in called_functions: print "That function has already been called" else: called_functions.add(func) func() 

Un enfoque orientado a objetos y convierte su función en una clase, también conocida como “functor”, cuyas instancias realizan un seguimiento automático de si se han ejecutado o no cuando se crea cada instancia.

Debido a que su pregunta actualizada indica que puede necesitar muchos de ellos, actualicé mi respuesta para lidiar con eso usando un patrón de fábrica de clase . Esto es un poco inusual, e iy puede haber sido rechazado por esa razón (aunque nunca lo sabremos con seguridad porque nunca dejaron un comentario). También se podría hacer con una metaclase, pero no es mucho más simple.

 def RunOnceFactory(): class RunOnceBase(object): # abstract base class _shared_state = {} # shared state of all instances (borg pattern) has_run = False def __init__(self, *args, **kwargs): self.__dict__ = self._shared_state if not self.has_run: self.stuff_done_once(*args, **kwargs) self.has_run = True return RunOnceBase if __name__ == '__main__': class MyFunction1(RunOnceFactory()): def stuff_done_once(self, *args, **kwargs): print("MyFunction1.stuff_done_once() called") class MyFunction2(RunOnceFactory()): def stuff_done_once(self, *args, **kwargs): print("MyFunction2.stuff_done_once() called") for _ in range(10): MyFunction1() # will only call its stuff_done_once() method once MyFunction2() # ditto 

Salida:

 MyFunction1.stuff_done_once() called MyFunction2.stuff_done_once() called 

Nota: Podrías hacer que una función / clase pueda hacer cosas de nuevo agregando un método reset() a su subclase que restablece el atributo has_run compartido. También es posible pasar argumentos regulares y de palabras clave al método stuff_done_once() cuando se crea el functor y se llama al método, si se desea.

Y, sí, sería aplicable dada la información que agregó a su pregunta.

Tienes todas esas ‘variables no deseadas’ fuera de tu línea principal while True loop. Para hacer que el código sea más fácil de leer, esas variables se pueden colocar dentro del bucle, justo al lado de donde se usan. También puede configurar una convención de nomenclatura variable para estos interruptores de control de progtwig. Así por ejemplo:

 # # _already_done checkpoint logic try: ran_this_user_request_already_done except: this_user_request() ran_this_user_request_already_done = 1 

Tenga en cuenta que en la primera ejecución de este código, la variable ran_this_user_request_already_done no se define hasta después de que se llama a this_user_request() .

Una función simple que puede reutilizar en muchos lugares de su código (según las otras respuestas aquí):

 def firstrun(keyword, _keys=[]): """Returns True only the first time it's called with each keyword.""" if keyword in _keys: return False else: _keys.append(keyword) return True 

o equivalente (si te gusta confiar en otras bibliotecas):

 from collections import defaultdict from itertools import count def firstrun(keyword, _keys=defaultdict(count)): """Returns True only the first time it's called with each keyword.""" return not _keys[keyword].next() 

Uso de la muestra:

 for i in range(20): if firstrun('house'): build_house() # runs only once if firstrun(42): # True print 'This will print.' if firstrun(42): # False print 'This will never print.' 

He tomado un enfoque más flexible inspirado en la función functools.partial :

 DO_ONCE_MEMORY = [] def do_once(id, func, *args, **kwargs): if id not in DO_ONCE_MEMORY: DO_ONCE_MEMORY.append(id) return func(*args, **kwargs) else: return None 

Con este enfoque puede tener interacciones más complejas y explícitas:

 do_once('foobar', print, "first try") do_once('foo', print, "first try") do_once('bar', print, "second try") # first try # second try 

La parte emocionante de este enfoque puede usarse en cualquier lugar y no requiere fábricas, es solo un pequeño rastreador de memoria.

Si la comprobación de la condición solo debe realizarse una vez que se encuentra en el bucle, es útil tener una señalización que indique que ya ha ejecutado la función. En este caso, usó un contador, una variable booleana funcionaría igual de bien.

 signal = False count = 0 def callme(): print "I am being called" while count < 2: if signal == False : callme() signal = True count +=1 

No estoy seguro de haber comprendido tu problema, pero creo que puedes dividir el bucle. En la parte de la función y la parte sin ella y guardar los dos bucles.