2010-12-02 14 views
4

Ich habe vor kurzem einen @sequenceable Decorator erstellt, der auf jede Funktion angewendet werden kann, die ein Argument akzeptiert, und bewirkt, dass sie automatisch auf jede Sequenz angewendet werden kann. Dies ist der Code (Python 2.5):Wie schrecklich ist mein Dekorateur?

def sequenceable(func): 
    def newfunc(arg): 
     if hasattr(arg, '__iter__'): 
      if isinstance(arg, dict): 
       return dict((k, func(v)) for k, v in arg.iteritems()) 
      else: 
       return map(func, arg) 
     else: 
      return func(arg) 
    return newfunc 

Im Einsatz:

@sequenceable 
def unixtime(dt): 
    return int(dt.strftime('%s')) 

>>> unixtime(datetime.now()) 
1291318284 
>>> unixtime([datetime.now(), datetime(2010, 12, 3)]) 
[1291318291, 1291352400] 
>>> unixtime({'start': datetime.now(), 'end': datetime(2010, 12, 3)}) 
{'start': 1291318301, 'end': 1291352400} 

Meine Fragen sind:

  • Ist dies eine schreckliche Idee, und warum?
  • Ist das vielleicht eine gute Idee, hat aber erhebliche Nachteile wie implementiert?
  • Was sind die möglichen Fallstricke von mit diesem Code?
  • Gibt es eine eingebaute oder Bibliothek, die bereits tut, was ich tue?
+0

Ist das nicht, was 'map' tut? Und listet das nicht auf? Warum fügen Sie einem bereits überfüllten Konzeptbereich eine dritte Technik hinzu? Bitte erklären Sie, wie sich dies gegenüber der Karten- und Listenkomprehension verbessert. –

+0

Es verbessert nicht über map(). Es ist eine Kurzschrift * für * map(). Ich stimme map() wahrscheinlich nicht, aber eine Kurzschrift für Iteritems() -> Liste Verständnis -> dict() könnte einen Verdienst haben. –

Antwort

8

Diese ist eine schreckliche Idee. Dies ist im Wesentlichen loses Tippen. Duck-Typisierung ist so weit wie diese Zeug sollte genommen werden, IMO.

Bedenken Sie:

def pluralize(f): 
    def on_seq(seq): 
     return [f(x) for x in seq] 
    def on_dict(d): 
     return dict((k, f(v)) for k, v in d.iteritems()) 
    f.on_dict = on_dict 
    f.on_seq = on_seq 
    return f 

Ihr Beispiel wird dann

@pluralize 
def unixtime(dt): 
    return int(dt.strftime('%s')) 


unixtime.on_seq([datetime.now(), datetime(2010, 12, 3)]) 
unixtime.on_dict({'start': datetime.now(), 'end': datetime(2010, 12, 3)}) 

es zu tun auf diese Weise noch den Anrufer verlangt zu wissen, (innerhalb von Ente-Typisierung Genauigkeit), was in übergeben wird und doesn Fügen Sie der eigentlichen Funktion keinen zusätzlichen Tippfehler hinzu. Es funktioniert auch mit jedem dict-ähnlichen Objekt, während Ihre ursprüngliche Lösung davon abhängt, dass es sich um eine tatsächliche Unterklasse von dict handelt.

+0

Ich denke du meinst def unixtime() dekoriert von @pluralize. Ich habe dieses Beispiel ausprobiert und es hat so funktioniert, was ich in Bezug auf Erweiterbarkeit erreichen wollte * und * explizit machen, was ich zu tun versuchte. Gute Show! –

+0

Das ist eine gute Idee, denke ich. – werehuman

+0

@ Brent Newey. Danke für die Korrektur :) Schön, dass es dir gefällt. – aaronasterling

1

Ich bin kein großer Fan von versuchen, Anrufer zu viel zu helfen. Python ist ausdrucksvoll genug, dass es für den Anrufer keine große Sache ist, die "Listenbildung" zu handhaben. Es ist einfach genug für einen Anrufer, die dict Verständnis oder map Aufruf schreiben.

Als Python-Programmierer, was ich erwarten würde zu tun, da die Python-Standard-Bibliothek-Funktionen mir nicht auf diese Weise helfen. Dieses Idiom macht mich etwas verwirrter, weil ich jetzt versuchen muss mich zu erinnern, welche Methoden "hilfreich" sind und welche nicht.

Zu flexibel zu sein ist ein kleiner Fehler, den ich mit dem Python-basierten Build-Tool SCons habe. Seine Methoden sind alle sehr entgegenkommend. Wenn Sie einige Preprocessor-Defines definieren wollen, können Sie ihm einen String, eine Liste von Strings, Tupeln, ein Diktat usw. geben. Es ist sehr praktisch, aber ein wenig überwältigend.

+0

Das war meine erste Reaktion auch; aber siehe Brents letztes Beispiel. Sein Dekorator "mappt" über ein Wörterbuch hinweg und verknüpft jeden Schlüssel mit dem Ergebnis der Funktion auf dem Wert des Schlüssels. (Ich denke ...) –

+0

Wir haben eine Menge von Dienstprogrammen, die auf ein Stück Daten einwirken, Zeichen entschlüsseln, Strings scrubben, Datumsangaben konvertieren usw., und am Ende machen wir viele Maps, listen die Comprehensions auf und konvertieren von dicts zu iterables und zurück, also dachte ich, dies könnte die Menge an überschüssigem Code reduzieren, nur um diese häufigen Aufgaben zu erfüllen. –

+0

@Dan Das ist richtig. Es ist eine Abkürzung für Funktionen, die häufig über Datenstrukturen hinweg zugeordnet sind. –

2

Meiner Meinung nach scheinen Sie Logik in den falschen Ort zu bauen. Warum sollte unixtime etwas über Sequenzierung wissen? In einigen Fällen wäre es eine gute Idee (für Leistung oder sogar Semantik), aber hier scheint es, als würden Sie zusätzliche Funktionen zu unixtime hinzufügen, die in diesem Kontext keinen Sinn ergeben.

Besser ist nur zu verwenden (beispielsweise) eine Liste Verständnis:

[unixtime(x) for x in [datetime.now(), datetime(2010, 12, 3)]] 

auf diese Weise, sind Sie die richtige Pythonic konstruieren für die Anwendung, die gleiche Sache zu einer Sequenz verwendet wird, und Sie verschmutzen nicht unixtime mit Ideen über Sequenzen. Sie beenden die Kopplungslogik (über die Sequenzierung) an Stellen, an denen die Implementierung frei von diesem Wissen sein sollte.

EDIT: Es kommt im Grunde auf Codierung Stil, Wiederverwendbarkeit und Wartbarkeit. Sie wollen gut partitionierten Code, so dass Sie bei der Codierung unixtime (sagen wir) ausschließlich mit der Konvertierung in eine unixtime betroffen sind. Wenn Sie sich für Sequenzen interessieren, entwerfen Sie Funktionalität (oder verwenden Sie die integrierte Syntax), die sich ausschließlich mit Sequenzen befasst. Das macht es einfacher, über jede Operation klar nachzudenken, Code zu testen, zu debuggen und wiederzuverwenden. Denken Sie darüber nach, sogar in Bezug auf den Namen: die ursprüngliche Funktion heißt unixtime, aber Ihre sequenzierte Version könnte passender unixtime_sequence genannt werden, was seltsam ist und eine ungewöhnliche Funktion vorschlägt.

Manchmal brechen Sie natürlich diese Regel. Wenn (aber nur wenn) Leistung ein Problem ist, können Sie Funktionalität kombinieren. Aber im Allgemeinen führt die Aufteilung in klare Teile zu klarem Denken, klarer Codierung und einfacher Wiederverwendung.

+0

OK, also schätze ich dieses Argument. Ich bin daran interessiert zu wissen, warum das eine schlechte Sache ist. Können Sie mir ein Beispiel geben, wo dies das Problem verwirren oder in meinem Gesicht explodieren würde? –

+0

@Brent: habe einige weitere Details hinzugefügt. – Peter

0

Nicht pythonic weil: - in Python explizit besser berücksichtigt als implizite - es ist nicht das Standard-Idiom wie builtin Karte unterhalten oder eine Liste Verständnis

0
@sequence 
def distance(points): 
    return sqrt(reduce(
     lambda a, b: a + b, 
     (a**2 for a in points), 
     0)) 

Und Ihr Dekorateur wird nutzlos. Ihr Decorator kann nur für spezielle Fälle verwendet werden, und wenn Sie ihn verwenden, werden Sie eine der Regeln von Python Zen brechen: "Es sollte eine - und vorzugsweise nur eine - offensichtliche Möglichkeit geben, dies zu tun".

+0

Der Decorator soll nur Funktionen ausführen, die skalare Werte annehmen.Aber ich verstehe das Argument. –