2016-04-25 19 views
8

Ho implementato l'attraversamento del grafico come una funzione generatore che restituisce il nodo visitato.Utilizzo del generatore send() all'interno di un ciclo for

A volte l'utente deve dire alla funzione di attraversamento che i bordi in uscita da un particolare nodo non devono essere seguiti; per supportarlo, l'attraversamento controlla il valore inviato ad esso (utilizzando il metodo generatore send()) e se è True, considera il nodo come una foglia per gli attraversamenti.

Il problema è che il ciclo utente più semplice è un pò lunga:

# simplified thanks to @tobias_k 
# bfs is the traversal generator function 
traversal = bfs(g, start_node) 
try: 
    n = next(traversal) 
    while True: 
    # process(n) returns True if don't want to follow edges out of n 
    n = traversal.send(process(n)) 
except StopIteration: 
    pass 

Esiste un modo per migliorare questa?

ho pensato qualcosa di simile dovrebbe funzionare:

for n in bfs(g, start_node): 
    ???.send(process(n)) 

ma mi sento che mi manca la conoscenza di una sintassi Python.

+2

Beh, potresti renderlo molto più breve mettendo il try/tranne al di fuori del ciclo; questo ti farà risparmiare un set di try/except e if-condition. –

+0

@tobias_k corretto, grazie. – max

risposta

3

Non vedo un modo per farlo in un ciclo regolare for. Tuttavia, è possibile creare un altro generatore, che itera un altro generatore, utilizzando alcune "funzioni seguite" per determinare se seguire l'elemento corrente, incapsulando così le parti difficili del codice in una funzione separata.

def checking_generator(generator, follow_function): 
    try: 
     x = next(generator) 
     while True: 
     yield x 
     x = generator.send(follow_function(x)) 
    except StopIteration: 
     pass 

for n in checking_generator(bfs(g, start_node), process): 
    print(n) 
+0

Questo funziona! Immagino che l'unico svantaggio, oltre a dover creare una funzione di utilità extra, sia che l'ipotetico '???. Send()' possa essere usato in molti punti del ciclo, costringendo il loop a continuare. Con questo approccio, il valore può essere inviato solo alla fine del ciclo. Peccato che Python manchi della sintassi per supportare un caso d'uso di base. – max

+0

@max è possibile inviare valori aggiuntivi nel generatore mantenendo un riferimento a quello originale: 'traversal = bfs (g, start_node); per n in checking_generator (traversal, process): ... traversal.send (...) 'anche se in questo caso' checking_generator' continuerà a elaborare in base all'ultimo nodo che ha elaborato. –

+1

@tobias_k quando termina una funzione generatrice, solleva una StopIteration. In modo da avvolgere l'intero corpo del codice in un 'try' per sopprimere un' StopIteration' ed uscire dalla funzione ...che quindi solleva un "StopIteration" sembra un po 'sciocco. :) –

1

non vedo questo come un caso d'uso frequente, considerare questo come il generatore originale:

def original_gen(): 
    for x in range(10): 
     should_break = yield x 
     if should_break: 
      break 

Se il valore di should_break viene sempre calcolato sulla base di una certa chiamata di funzione con x allora perché non solo scrivere il generatore in questo modo:

def processing_gen(check_f): 
    for x in range(10): 
     yield x 
     should_break = check_f(x) 
     if should_break: 
      break 

Tuttavia di solito penso al codice che elabora i valori generati come scritti all'interno del ciclo (altrimenti qual è il punto di avere un anello a tutti?)

Che sembra davvero si vuole fare è creare un generatore in cui si chiama il metodo __next__ davvero implica send(process(LAST_VALUE)) che può essere implementato con una classe:

class Followup_generator(): #feel free to use a better name 
    def __init__(self,generator,following_function): 
     self.gen = generator 
     self.process_f = following_function 
    def __iter__(self): 
     return self 
    def __next__(self): 
     if hasattr(self,"last_value"): 
      return self.send(self.process_f(self.last_value)) 
     else: 
      self.last_value = next(self.gen) 
      return self.last_value 
    def send(self,arg): 
     self.last_value = self.gen.send(arg) 
     return self.last_value 
    def __getattr__(self,attr): 
     "forward other lookups to the generator (.throw etc.)" 
     return getattr(self.gen, attr) 

# call signature is the exact same as @tobias_k's checking_generator 
traversal = Followup_generator(bfs(g, start_node), process) 
for n in traversal: 
    print(n) 
    n = traversal.send(DATA) #you'd be able to send extra values to it 

Tuttavia, questo ancora non vede questo come di uso frequente, sarei perfettamente bene con un ciclo while, anche se avevo messo la chiamata .send in alto:

traversal = bfs(g, start_node) 
send_value = None 
while True: 
    n = traversal.send(send_value) 
    #code for loop, ending in calculating the next send_value 
    send_value = process(n) 

E si potrebbe avvolgere che in un try: ... except StopIteration:pass anche se trovo che semplicemente in attesa di un errore di sollevare è meglio espressa con un contesto manager:

class Catch: 
    def __init__(self,exc_type): 
     if issubclass(exc_type,BaseException): 
      self.catch_type = exc_type 
     else: 
      raise TypeError("can only catch Exceptions") 
    def __enter__(self): 
     return self 
    def __exit__(self,exc_type,err, tb): 
     if issubclass(exc_type, self.catch_type): 
      self.err = err 
      return True 


with Catch(StopIteration): 
    traversal = bfs(g, start_node) 
    send_value = None 
    while True: 
     n = traversal.send(send_value) 
     #code for loop, ending in calculating the next send_value 
     send_value = process(n) 
2

Per semplificare il codice del client, è possibile utilizzare un bsf() generatore di ordinaria e di controllo node.isleaf attributo in essa:

for node in bfs(g, start_node): 
    node.isleaf = process(node) # don't follow if `process()` returns True 

Lo svantaggio è che node è mutevole. Oppure devi passare una struttura dati condivisa che tiene traccia dei nodi foglia: leaf[node] = process(node) dove il dizionario leaf viene passato in precedenza a bfs().

Se si desidera utilizzare il metodo .send() in modo esplicito; devi gestire StopIteration. Vedi PEP 479 -- Change StopIteration handling inside generators. Si poteva nasconderlo in una funzione di supporto:

def traverse(tree_generator, visitor): 
    try: 
     node = next(tree_generator) 
     while True: 
      node = tree_generator.send(visitor(node)) 
    except StopIteration: 
     pass 

Esempio:

traverse(bfs(g, start_node), process) 
0

ho scoperto che la mia domanda avrebbe avuto una risposta di una riga, utilizzando l'estensione "continua" dichiarazione proposto nel earlier version of PEP 342 :

for n in bfs(g, start_node): 
    continue process(n) 

Tuttavia, mentre PEP 342 è stata accettata, quella caratteristica particolare è stata ritirata dopo this June 2005 discussion tra Raymond e Guido:

Raymond Hettinger detto:

Lasciami andare su record come un forte -1 per "continuare EXPR". Il ciclo for è il nostro costrutto più basilare ed è facilmente comprensibile nel suo modulo attuale . Lo stesso si può dire per "continua" e "pausa" che hanno il vantaggio aggiunto di una curva di apprendimento vicino allo zero per le persone che migrano da altre lingue.

Qualsiasi urgenza di complicare queste dichiarazioni di base dovrebbe essere seriamente valutata e mantenuta a standard elevati di chiarezza, spiegabilità, ovvietà, utilità e necessità. IMO, fallisce la maggior parte dei test .

Non vedrei l'ora di spiegare "continua ESPR" nel tutorial e penso che risalterebbe come anti-funzionalità.

[...] L'argomento corretto rispetto a "continua ESPR" è che ci sono ancora ; se ci fosse un buon caso d'uso, la spiegazione seguirà facilmente.

Guido

Se sviluppatori principali di pitone da allora hanno cambiato idea circa l'utilità di esteso "continua", forse questo potrebbe essere reintrodotto in un futuro PEP. Ma, dato un caso d'uso quasi identico come in questa domanda è stato già discusso nel thread citato, e non è stato trovato persuasivo, sembra improbabile.