2016-04-25 5 views
8

Ich implementierte Graph Traversal als eine Generatorfunktion, die den Knoten ergibt, der besucht wird.Verwendung von Generator send() innerhalb einer for-Schleife

Manchmal muss der Benutzer der Traversalfunktion mitteilen, dass die Kanten, die von einem bestimmten Knoten ausgehen, nicht befolgt werden sollten; Um dies zu unterstützen, überprüft der Traversal den Wert, der an ihn zurückgesendet wird (unter Verwendung des Generators send() Methode), und wenn er True ist, betrachtet er den Knoten als ein Blatt für Traversierungszwecke.

Das Problem ist, dass die einfachste Benutzer Schleife ein bisschen lang ist:

# 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 

Gibt es eine Möglichkeit, dies zu verbessern?

Ich dachte, so etwas wie dies funktionieren sollte:

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

aber ich glaube, ich bin das Wissen einiger Python Syntax fehlt.

+2

Nun, Sie könnten es viel kürzer machen, indem Sie versuchen/außer außerhalb der Schleife; Das wird dir eine Menge von try/exception und der if-Bedingung sparen. –

+0

@tobias_k behoben, danke. – max

Antwort

3

Ich sehe keinen Weg, dies in einer normalen for Schleife zu tun. Sie könnten jedoch einen anderen Generator erstellen, der einen anderen Generator iteriert und dabei eine "follow-function" verwendet, um zu bestimmen, ob dem aktuellen Element gefolgt werden soll. Dadurch werden die komplizierten Teile Ihres Codes in eine separate Funktion eingekapselt.

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

Das funktioniert! Ich denke, der einzige Nachteil, abgesehen von einer zusätzlichen Dienstprogrammfunktion, besteht darin, dass das hypothetische "???. Send()" an vielen Stellen in der Schleife verwendet werden könnte, wodurch die Schleife gezwungen wird, fortzufahren. Bei diesem Ansatz kann der Wert nur am Ende der Schleife gesendet werden. Zu schade, Python fehlt die Syntax, um einen solchen grundlegenden Anwendungsfall zu unterstützen. – max

+0

@max Sie können zusätzliche Werte in den Generator senden, indem Sie einen Verweis auf den ursprünglichen beibehalten: 'traversal = bfs (g, start_node); für n in checking_generator (traversal, process): ... traversal.send (...) 'obwohl 'checking_generator' in diesem Fall immer noch auf der Basis des letzten verarbeiteten Knotens arbeitet. –

+1

@tobias_k Wenn eine generierende Funktion endet, wird eine StopIteration ausgelöst. Um also den gesamten Code in einen "Versuch" zu überführen, eine "StopIteration" zu unterdrücken und die Funktion zu beenden ...was dann eine "StopIteration" auslöst, scheint etwas albern zu sein. :) –

1

Ich sehe dies nicht als häufiger Anwendungsfall, das wie der Original-Generator betrachten:

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

Wenn der Wert von should_break immer dann mit x auf einig Funktionsaufruf basierend berechnet wird, warum schreibe den Generator nicht einfach so:

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

Allerdings denke ich normalerweise an den Code, der die generierten Werte als geschrieben verarbeitet innerhalb der Schleife (sonst was ist der Punkt, um eine Schleife an allen zu haben?)

Was wirklich scheint es, was Sie tun wollen, ist ein Generator zu schaffen, wo wirklich die __next__ Aufruf der Methode impliziert send(process(LAST_VALUE)), die mit einer Klasse implementiert werden kann:

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 

dies jedoch immer noch sieht dies nicht so häufig verwendet, ich mit einer while Schleife völlig in Ordnung sein würde, obwohl ich den .send Anruf an der Spitze gestellt habe:

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) 

Und Sie könnten, dass in einem try: ... except StopIteration:pass wickeln, obwohl ich für einen Fehler, dass nur darauf warten, finden sich besser mit einem Kontext-Manager zum Ausdruck zu erhöhen:

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

den Client-Code zu vereinfachen, können Sie einen gewöhnlichen bsf() Generator und Prüfung nutzen könnten node.isleaf Attribut darin:

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

Der Nachteil ist, dass node wandelbar ist. Oder Sie müssen eine gemeinsame Datenstruktur übergeben, die Blattknoten verfolgt: leaf[node] = process(node) wobei leaf Wörterbuch in bfs() früher übergeben wird.

Wenn Sie die Methode .send() explizit verwenden möchten; Sie müssen mit StopIteration umgehen. Siehe PEP 479 -- Change StopIteration handling inside generators. Man konnte es in einer Hilfsfunktion verbergen:

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

Beispiel:

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

entdeckte ich, dass meine Frage eine Antwort einzeilige gehabt hätte, mit der erweiterten „continue“ Erklärung vorgeschlagen in der earlier version of PEP 342 :

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

Doch während PEP 342 angenommen wurde, dass bestimmte Funktion nach this June 2005 discussion zwischen Raymond und Guido zurückgezogen wurde:

Raymond Hettinger sagte:

mich auf Rekord als starker -1 Gehen wir für "EXPR weiter". Die for-Schleife ist unser grundlegendstes Konstrukt und leicht in ihrer vorliegenden Form zu verstehen. Das gleiche kann für "weiter" und "Pause" gesagt werden, die den zusätzlichen Vorteil einer Null-Lernkurve für Menschen haben, die aus anderen Sprachen migrieren.

Jeder Drang, diese grundlegenden Aussagen zu komplizieren, sollte ernsthaft überprüft und zu hohen Standards der Klarheit, Erklärbarkeit, Offensichtlichkeit, Nützlichkeit und Notwendigkeit gehalten werden. IMO, es schlägt die meisten dieser Tests fehl.

Ich würde nicht erwarten, zu erklären, "EXPR fortsetzen" im Tutorial und denke, es würde als ein Anti-Feature auffallen.

[...] Das korrekte Argument gegen "EXPR fortfahren" ist, dass noch keine Anwendungsfälle sind; Wenn es einen guten Anwendungsfall gäbe, würde die Erklärung leicht folgen.

Guido

Wenn Python Kern-Entwickler über die Nützlichkeit von erweiterten „continue“, da geändert haben ihre Meinung vielleicht diese in eine Zukunft PEP wieder eingeführt werden könnte. Aber angesichts eines nahezu identischen Anwendungsfalls, wie er in dieser Frage bereits im zitierten Thread diskutiert wurde und nicht überzeugend gefunden wurde, erscheint dies unwahrscheinlich.

Verwandte Themen