2010-11-05 14 views
43

Al momento, sto facendo cose come la seguente, che è sempre noioso:modo efficiente di avere una funzione di eseguire solo una volta in un ciclo

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

Sto indovinando c'è qualche modo più accettato di gestire questa roba?

Quello che sto cercando è eseguire una funzione una volta, su richiesta. Ad esempio, alla pressione di un determinato pulsante. È un'app interattiva che ha molti switch controllati dall'utente. Avere una variabile spazzatura per ogni interruttore, solo per tenere traccia del fatto che sia stato eseguito o meno, sembrava un po 'inefficiente.

+8

Se è necessario eseguire una volta, perché non chiamare la funzione -> myFunction() una volta !!. Si prega di dare un'occhiata al codice, spiegare meglio l'intento.! – pyfunc

+4

Perché non puoi lasciarlo fuori dal giro? Più contesto per favore. – EightyEight

+0

@pyfunc Fammi sapere se la modifica del codice trasmette più chiaramente il problema. – Ron

risposta

76

vorrei usare un decoratore sulla funzione per gestire tenere traccia di quante volte si corre.

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 

Ora my_function verrà eseguito una sola volta. Altre chiamate ad esso restituiranno None. Basta aggiungere una clausola else allo if se si desidera che restituisca qualcos'altro. Dal tuo esempio, non ha bisogno di restituire nulla mai.

Se non si controlla la creazione della funzione o la funzione deve essere utilizzata normalmente in altri contesti, è possibile applicare manualmente anche il decoratore.

Questo lascerà my_function disponibile per altri usi.

Infine, se è necessario eseguire solo una volta due volte, allora si può solo fare

action = run_once(my_function) 
action() # run once the first time 

action.has_run = False 
action() # run once the second time 
+0

Questo sembra quello che potrei cercare, ma sono incerto sulla sintassi di ciò che stai usando. A cosa serve @ char? –

+4

@Marcus Ottosson Il '@' char lo rende un decoratore. Un commento è troppo lungo per spiegare cos'è. Qui, hai un [link] (http://www.artima.com/weblogs/viewpost.jsp?thread=240808). In ogni caso, potresti semplicemente voler usare il secondo modulo che ho presentato invece dove lo applichi manualmente. Probabilmente dovresti sapere ancora che cos'è un decoratore. – aaronasterling

+0

Bene, ci penserò. Grazie –

6

Eseguire la funzione prima del ciclo. Esempio:

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

Questa è la soluzione più ovvia. Se c'è più di quanto sembri, la soluzione potrebbe essere un po 'più complicata.

+2

Questo non verrà interrotto perché l'OP indica "Ad esempio, premendo un determinato pulsante, si tratta di un'app interattiva con molti switch controllati dall'utente". Quindi l'OP vuole che la funzione sia eseguibile una volta dall'interno del ciclo di esecuzione principale. –

0

Perché questo è diverso dal codice?

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

Perché 'run_once' ha bisogno di essere ancora lì? –

+0

@Rafe Kettler: In realtà, non ho mai avuto intenzione di rispondere ma non è possibile visualizzare il codice nei commenti. Ho fatto un commento ma ho pensato che potrei chiedere anche a lui. Non sono sicuro che questa fosse una domanda. – pyfunc

+1

forse non lo sapremo mai. –

-2

Non sono sicuro di aver capito il problema, ma penso che sia possibile dividere il ciclo. Da parte della funzione e la parte senza di essa e salva i due loop.

+0

Questo non risponde alla domanda. Se non capisci il problema, chiedi chiarimenti in un commento. –

+0

I secondo Jamie. Pubblica un commento se hai bisogno di ulteriori chiarimenti. –

5

Suppongo che si tratti di un'azione che si desidera eseguire al massimo una volta, se sono soddisfatte alcune condizioni. Dal momento che non eseguirai sempre l'azione, non potrai farlo incondizionatamente fuori dal giro. Qualcosa come recuperare pigramente alcuni dati (e memorizzarli nella cache) se ottieni una richiesta, ma non la recuperi in altro modo.

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

@Ryan Ginstrom: Davvero !! – pyfunc

+0

Se potessi inversione di due volte ... –

+1

Questo presuppone che tu voglia (o potresti) cambiare la logica di do_something() o myFunction() per adattarsi a questo caso particolare. Forse do_something/myFunction è chiamato altrove in un contesto diverso. (Ovviamente non lo sapremo fino a quando il manifesto non chiarirà.) – snapshoe

1

presumere che vi sia qualche ragione per cui myFunction() non può essere chiamato prima del ciclo

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

Idea molto intelligente - ma sembra che l'OP desideri qualcosa che è un aspetto della funzione e in effetti per ogni chiamata ad esso, piuttosto che essere controllato esternamente da un punto come questo. – martineau

-1

Se il controllo condizione deve accadere solo una volta che siete nel ciclo, avendo una segnalazione bandiera che hai già eseguire la funzione aiuta. In questo caso hai usato un contatore, una variabile booleana funzionerebbe altrettanto bene.

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

Non è esattamente lo stesso di quello che l'OP sta già facendo? –

4

Ci sono molti modi per fare quello che vuoi; tuttavia, si noti che è del tutto possibile che, come descritto nella domanda, non sia necessario chiamare la funzione all'interno del ciclo.

Se si insiste ad avere la chiamata di funzione all'interno del ciclo, si può anche fare:

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

+1 Sono tentato di cancellare la mia risposta ma di recente ne ho fatto un po 'troppo. – aaronasterling

+0

@aaron: sicuramente non dovresti cancellare la tua risposta, a prescindere dal rapporto delle risposte cancellate/rimanenti :) – tzot

+1

esplicito 'se needs_to_run non è None' e qui potrebbe essere appropriata la corretta formattazione. 'needs_to_run, run_once = None, needs_to_run; run_once() 'potrebbe proteggere dalle eccezioni, rendere meno probabili le chiamate multiple in risposta alle sequenze di tasti dell'utente. – jfs

0

Un approccio orientato agli oggetti e fare la vostra funzione di una classe, in arte come un "functor", le cui istanze automaticamente tenere traccia di se sono stati eseguiti o meno quando ogni istanza viene creata.

Poiché la tua domanda aggiornata indica che potrebbero essere necessari molti di essi, ho aggiornato la mia risposta per gestirlo utilizzando uno schema class factory. Questo è un po 'insolito, e potrebbe essere stato sottovalutato per questo motivo (anche se non lo sapremo mai con certezza perché non hanno mai lasciato un commento). Potrebbe anche essere fatto con un metaclass, ma non è molto più semplice.

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 

uscita:

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

Nota: Si potrebbe fare una funzione/classe in grado di fare ancora una volta cose con l'aggiunta di un metodo reset() alla sua sottoclasse che reimpostare l'attributo condiviso has_run. È anche possibile passare argomenti regolari e parole chiave al metodo stuff_done_once() quando viene creato il functor e viene chiamato il metodo, se lo si desidera.

E, sì, sarebbe applicabile date le informazioni che hai aggiunto alla tua domanda.

+0

Anche questo sembra abbastanza buono. Ti dispiacerebbe leggere la mia domanda aggiornata per vedere se potrebbe essere una buona soluzione? –

+0

@MarcusOttosson: Ci scusiamo per la risposta in ritardo. Se capisco le informazioni aggiuntive nella tua domanda aggiornata, allora sì, penso che funzionerebbe bene. – martineau

1

Ecco un modo esplicito per codificarlo, in cui lo stato di quali funzioni sono state chiamate viene mantenuto localmente (quindi lo stato globale viene evitato). Non mi piacciono molto le forme non esplicite suggerite in altre risposte: è troppo sorprendente vedere f() e per questo non significa che f() venga chiamata.

Questo funziona utilizzando dict.pop che cerca una chiave in un dict, rimuove la chiave dal dict e prende un valore predefinito da utilizzare nel caso in cui la chiave non venga trovata.

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

Ho pensato ad un altro modo molto efficace per eseguire questa operazione che non richiede funzioni o classi di decoratore. Invece usa solo un argomento di parole mutabili, che dovrebbe funzionare nella maggior parte delle versioni di Python. La maggior parte delle volte questi sono qualcosa da evitare poiché normalmente non si vorrebbe che un valore di argomento predefinito fosse in grado di passare da una chiamata all'altra, ma tale abilità può essere sfruttata e utilizzata come meccanismo di archiviazione a basso costo. Ecco come potrebbe funzionare:

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

my_function1(_has_run=[]) # force it to run 

# output: 
# my_function1 doing stuff 
# my_function2 doing some other stuff 
# my_function1 doing stuff 

Questo potrebbe essere semplificata un po 'più avanti facendo what @gnibbler suggested in his answer e l'utilizzo di un iteratore (che sono stati introdotti in Python 2.2.):

from itertools import count 

def my_function3(_count=count()): 
    if _count.next(): return 
    print "my_function3 doing something" 

for i in range(10): 
    my_function3() 

# my_function3 doing something 
+0

+1 per l'utilizzo di argomenti di parole chiave mutabili. –

0

Se ho capito la domanda aggiornato correttamente, qualcosa di simile dovrebbe funzionare

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

Ciò fermerebbe l'interattività. Vedi, questa app controlla l'input del mio tablet e lo elabora attraverso varie espressioni e matematica di base e infine lo visualizza sullo schermo. Penso che quello che sto cercando non sia molto particolare in alcun modo, perché tutto ciò che sto cercando di fare è, per esempio, alla pressione di B, aggiungere 2 a qualsiasi variabile in modo che l'equazione cambi. Oppure, modificare il colore di una delle barre che rappresentano alcuni dati sullo schermo. Sto iniziando a pensare che potrebbe non esserci un altro modo di farlo rispetto a quello che sto facendo attualmente. –

+0

Quindi, per chiarire, non deve solo eseguire Once e quindi non essere mai in grado di eseguire nuovamente. Ha solo bisogno di fare la cosa all'interno del ciclo, e poi smettere di farlo. Come, aggiungi 2 alla mia equazione, quindi continua a fare qualsiasi cosa tu stia facendo prima, ma con i nuovi dati immessi dall'utente. Cose facili, penserei :) –

+0

@Marcus, non sto suggerendo che _you_ dovrebbe usare 'raw_input()'. È lì solo così puoi eseguire questo programma e avere un'idea di come funziona. Mi sembra che la tua app funzioni meglio con i callback anche se –

2

Ecco una risposta che non comporta riassegnazione delle funzioni, ma impedisce ancora la necessità di quella brutta "è F prima "controlla".

__missing__ è supportato da Python 2.5 e versioni successive.

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 

uscita:

performing varname1 
only done once for varname1 
performing varname2 
only done once for varname2 
only done once for varname1 
only done once for varname2 
+0

+1 per me che sto imparando qualcosa di nuovo. –

13

Un'altra opzione è quella di impostare il func_codecode object per la funzione di essere un oggetto di codice per una funzione che non fa nulla. Questo dovrebbe essere fatto alla fine del tuo corpo funzione.

Ad esempio:

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

Qui run_once.func_code = (lambda:None).func_code sostituisce il codice eseguibile del vostro funzione con il codice per lambda: Nessuno, quindi tutte le chiamate successive a run_once() farà nulla.

Questa tecnica è meno flessibile dell'approccio decoratore suggerito nello accepted answer, ma potrebbe essere più conciso se si ha una sola funzione che si desidera eseguire una volta.

+0

Ho imparato qualcosa di nuovo, grazie. :) –

0

Per risolvere il problema. avete solo bisogno di cambiare un po 'di codice:

run_once = 0 
while run_once < 1: 
     print("Hello") 
     run_once = 1 
0

Hai tutto al di fuori della vostra linea principale while True ciclo quei' variabili spazzatura. Per rendere il codice più facile da leggere, è possibile portare tali variabili all'interno del ciclo, proprio accanto a dove vengono utilizzate. È anche possibile impostare una convenzione di denominazione delle variabili per questi interruttori di controllo del programma. Così, per esempio:

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

noti che nella prima esecuzione di questo codice variabile ran_this_user_request_already_done non è definito solo dopo this_user_request() è chiamato.

Problemi correlati