2015-12-16 6 views
11

So bin ich neugierig auf die Ansichten von erfahrenen Python-Programmierer auf die folgende Stil Frage. Angenommen, ich baue eine Funktion, die Zeile für Zeile durch einen Pandas-Datenrahmen oder einen ähnlichen Anwendungsfall iteriert, in dem eine Funktion Zugriff auf ihren vorherigen Zustand benötigt. Es scheint mindestens vier Möglichkeiten zur Umsetzung dieses in Python zu sein:Idiome in Python: Schließung vs Funktor vs Objekt

1) Verschlüsse:

def outer(): 
    previous_state = None 
    def inner(current_state) : 
     nonlocal previous_state 
     #do something 
     previous_state=current_state 
     return something 

Also, wenn Sie von einem JavaScript-Hintergrund kommen wird dies zweifellos natürlich erscheinen zu Ihnen. Es fühlt sich auch in Python ziemlich natürlich an, bis Sie auf den umschließenden Bereich zugreifen müssen, wenn Sie am Ende etwas wie inner.__code__.co_freevars tun werden, das Ihnen die Namen Ihrer einschließenden Variablen als Tupel und den Index des einen geben wird Sie wollen und gehen dann zu inner.__closure__[index].cell_contents, um seinen Wert zu erhalten. Nicht gerade elegant, aber ich nehme an, es geht oft darum, den Umfang zu verbergen, also ist es sinnvoll, dass es schwer zu erreichen ist. Auf der anderen Seite fühlt es sich ein wenig komisch an, dass Python die umschließende Funktion privat macht, wenn es fast jede andere Möglichkeit, eine private Variable im Vergleich zu OOP-Sprachen zu haben, beseitigt hat.

2) Functor

def outer(): 
    def inner(current_state): 
     #do something 
     inner.previous_state=current_state 
     return something 
    ret = inner 
    ret.previous_state=None 
    return ret 

Dieses „öffnet den Verschluss“, dass jetzt der umschließenden Zustand vollständig sichtbar als ein Attribut der Funktion. Das funktioniert, weil Funktionen wirklich nur getarnte Objekte sind. Ich lehne mich dazu als das pythischste an. Es ist klar, prägnant und lesbar.

3) Objekte Dies ist wahrscheinlich die am meisten vertraut OOP-Programmierer

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def do_something(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

Der größte con ist, dass Sie mit einer Menge von Klassendefinitionen, um am Ende neigen. Das ist in einer vollständigen OOP-Sprache wie Java gut, wo Sie Interfaces und Ähnliches haben, um das zu managen, aber in einer entarteten Sprache scheint es ein bisschen seltsam zu sein, viele einfache Klassen zu haben, nur um eine Funktion mit etwas Status zu transportieren.

4) Globals - Ich werde das nicht zeigen, wie ich den globalen Namen verschmutzen Raum

5) Dekorateure speziell vermeiden wollen - das ist ein bisschen ein Curveball, aber Sie können Dekorateure benutzen teilweise zu speichern, Zustandsinformationen.

@outer 
def inner(previous_state, current_state): 
    #do something 
    return something 

def outer(inner) : 
    def wrapper(current_state) : 
     result = inner(wrapper.previous_state, current_state) 
     wrapper.previous_state = current_state 
     return result 
    ret = wrapper 
    ret.previous_state=None 
    return result 

Diese Art der Syntax ist die am wenigsten mir bekannt vor, aber wenn ich

func = inner 

ich nenne jetzt eigentlich

func = outer(inner) 

und dann func() wirkt ebenso wie die Funktors wiederholt aufrufen Beispiel. Ich hasse diesen Weg wirklich am meisten. Es scheint mir eine wirklich intransparente Syntax zu haben, in der es nicht klar ist, ob der Aufruf von inner (current_state) viele Male das selbe Ergebnis liefert oder ob es Ihnen jedes Mal eine neu dekorierte Funktion gibt, also scheint es eine schlechte Übung zu sein Dekoratoren, die auf diese Weise einen Zustand zu einer Funktion hinzufügen.

Also was ist der richtige Weg? Welche Vor- und Nachteile habe ich hier verpasst?

+1

Ich bin nicht ganz folgen, warum Sie denken, Sie müssen auf geschlossene Variablen zugreifen über 'innere .__ closure__'; Namen enden nur in dieser Struktur, weil Sie sie aktiv in der inneren Funktion * schon * verwenden. Die '__closure__'-Struktur ist wirklich ein internes Implementierungsdetail. –

+0

Ich lasse die Leute das entwickeln, aber aus Erfahrung neigen Python-Entwickler dazu, die Klasse für diese Art von Szenario zu verwenden. Oder Dekorateure, wenn Sie keine volle Klarheit benötigen und die nette Syntax mit '@' bevorzugen. Sie möchten kein nichtlokales oder globales Schlüsselwort verwenden. Und Funktor ist oft offen für die Schließung. – Cyrbil

+0

Geschlossene Namen unterscheiden sich in dieser Hinsicht nicht von Einheimischen; Sie kommen normalerweise nicht in die Lage, ihre Einheimischen zu lesen, warum tun Sie das Gleiche mit den Schließungen? Wenn Sie diesen Status für die Verwendung außerhalb der Funktion verfügbar machen müssen, verwenden Sie den Funktor- oder Klassenansatz. –

Antwort

3

Also die richtige Antwort darauf ist das Callable-Objekt, das im Wesentlichen das Idiom der Schließung in Python ersetzt.

so Abarbeiten Option 3 oben Änderung:

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def do_something(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

zu

class Calculator(Object) : 
    def __init__(self): 
     self.previous_state=None 

    def __call__(self, current_state) : 
     #do_something 
     self.previous_state = current_state 
     return something 

und jetzt können Sie es wie eine Funktion aufrufen. Also

2

Sie können einen Generator definieren, bei dem es sich um eine eingeschränkte Form eines Coprozesses handelt.

def make_gen(): 
    previous_state = None 
    for row in rows: 
     # do something 
     previous_state = current_state 
     yield something 

thing = make_gen() 
for item in thing: 
    # Each iteration, item is a different value 
    # "returned" by the yield statement in the generator 

Statt thing des Aufrufs (die ersetzt Ihre innere Funktion) wiederholt, wiederholen Sie über sie (die als Aufruf next(thing) wiederholt im Grunde das gleiche ist).

Der Zustand ist vollständig innerhalb des Generators enthalten.

Wenn Sie nicht wirklich darüber iterieren möchten, können Sie den Coprozess immer noch selektiv aufrufen, indem Sie explizit next aufrufen.

+0

Es ist nicht klar aus der Frage, wie genau dies im OP-Code verwendet werden würde. Ich vermute, dass die Zeilen aus dem Datenrahmen als Argument an 'make_gen' übergeben würden (im Moment habe ich keine Argumente, aber die freie Variable' rows' in der Schleife soll aus dem Datenrahmen stammen). – chepner