2010-12-20 16 views
6

Ich habe ein Modul, das eine Funktion hat, deren Prototyp ähnlich der der Klasse ist.Python Dekorator mit Optionen

def do(fn, argtuple=(), kwargdict={}, priority=0, 
      block=False, timeout=0, callback=None, daemon=False) 

    # do stuff 

fn ist eine aufrufbare und argtuple und kwargdict sind Lage- und Wörterbuch Argumente, die an die fn weitergegeben werden aufrufbar, wenn es aufgerufen wird.

Ich versuche jetzt einen Dekorateur dafür zu schreiben, aber ich bin verwirrt. Ich habe Dekorateure nie wirklich gut verstanden. Gibt es eine Möglichkeit, einen Dekorator zu machen, dass ich die Optionen in dem oben genannten wie Timeout festlegen kann, aber in Argaptuple und Kwargdict übergeben, wenn die Funktion aufgerufen wird.

So zum Beispiel:

@do(priority=2) 
def decoratedTask(arg, dic=3): 
    #do stuff 

decoratedTask(72) 

Ich bin verwirrt, wie ich die Laufzeit Argument 72 in die dekorierten Funktion übergeben würde. Ich denke, der Dekorator muss eine Klasse sein, in der die __call__-Methode den Funktionsaufruf zurückgibt, aber ich bin mir nicht sicher, wie man solche Argumente übergeben soll.

Macht das Sinn?

Antwort

14

Was der Dekorator tut ist, dass er die Funktion als Argument übernimmt und auch eine Funktion zurückgibt, typischerweise eine neue Funktion, die im Decorator erzeugt wird.

Diese neue Funktion muss die gleichen Parameter wie die Funktion, die Sie dekorieren, und muss auch die ursprüngliche Funktion aufrufen.

Jetzt, wenn Sie einen Dekorateur mit einem Argument haben, gibt es einen zusätzlichen Level. Dieser Dekorator sollte das Argument nehmen und einen Dekorator zurückgeben. Die Funktion, die Sie verwenden, ist in Wirklichkeit kein Dekorateur, sondern ein Dekorateur! Hier

ein Beispiel:

>>> def mydeco(count): 
...  def multipass(fn): 
...   def caller(*args, **kw): 
...    return [fn(*args, **kw) for x in range(count)] 
...   return caller 
...  return multipass 
... 
>>> @mydeco(5) 
... def printer(text): 
...  print(text) 
... 
>>> printer("Yabbadabbadoo!") 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
[None, None, None, None, None] 

Sie Beispiele für diese decoratormakers als Klassen implementiert sehen werden. Dass ich es auch bevorzuge (obwohl ich am Ende sowieso keinen Dekorateur habe), aber eine Funktion in einer Funktion in einer Funktion. :)

+0

Dank dieses hat es viel klarer gemacht. Ich werde nur mit tief verschachtelten Funktionsdefinitionen wie dieser verwirrt. Ich bin mir ziemlich sicher, dass ich das verstehe. Lass mich ein bisschen damit spielen, bevor ich das akzeptiere. – Falmarri

4

Das ist nicht ganz so, wie die Decorator-Syntax funktioniert. Wenn Sie @do(priority=2) schreiben, wertet Python aus und verwendet das Ergebnis dieses Aufrufs als Dekorator. Es ist eine Abkürzung für

decorator=do(priority=2) 
@decorator 

Sie wollen also tatsächlich do Dekorateur Fabrik machen: Sie alle Positionsargumente nehmen wollen und einen Dekorateur zurück.

def do(args=(), kwargs={}, ...): 
    def _decorator(fn): 
     def newfn(*args, **kwargs): 
      return fn(*args, **kwargs) 
     return newfn 
    return _decorator 

Hinweis, dass es tatsächlich drei Funktionen hier!

  • do die Funktion ist, dass wir unsere Dekorateur erhalten Telefonierens (so zB do(priority=2) ist ein Beispiel Dekorateur)
  • _decorator ist der eigentliche Dekorateur zurückgegeben, die
  • Da ein Dekorateur nimmt zu do auf die Argumente hängt eine Funktion als Eingabe und gibt eine Funktion als Ausgabe zurück, müssen wir die Funktion definieren, die der Dekorator zurückgibt. newfn ist diese Funktion.

Beispiel:

>>> def rename(name): 
...  def _decorator(fn): 
...    def renamed(*args, **kwargs): 
...      return fn(*args, **kwargs) 
...    renamed.__name__ = name 
...    return renamed 
...  return _decorator 
... 
>>> @rename('I like omelettes in the morning.') 
... def foo(): 
...  return 'bar' 
... 
>>> foo() 
'bar' 
>>> foo.__name__ 
'I like omelettes in the morning.' 

oder äquivalent

>>> omeletter = rename('I like omelettes in the morning.') 
>>> @omeletter 
... def foo(): 
...  return 'bar' 
... 
>>> foo() 
'bar' 
>>> foo.__name__ 
'I like omelettes in the morning.' 

By the way, kümmern sich mit den wandelbaren Standardargumente () und {}; Ich bin sicher, du kennst die Gefahren!

+0

katrielalex: '()' ist nicht wandelbar und ist als Standard nur in Ordnung. Du hast Recht mit '{}'. – Duncan

+0

@Duncan - wahr in der Tat! Danke =) – katrielalex

+0

Können Sie auf Artikel oder Dokumentation verweisen, die dieses Verhalten erklären. Ich verstehe nicht wirklich, wie Funktionsparameter in Python herumgereicht werden, und das erscheint mir etwas unlogisch. – moz

3

Wie die anderen Antworten erklärt haben, Dekorateure weitergegeben werden normalerweise ein einziges implizites Argument Funktion, wenn sie durch nur mit ihren Namen wie folgt aufgerufen:

@deco 
    def somefunc(...): pass 

was dasselbe tut wie:

def somefunc(...): pass 
    somefunc = deco(somefunc) 

Das Argument in dieser Situation ist eine kompilierte Version der Funktionsdefinition, die unmittelbar folgt. In solchen Fällen gibt der Decorator eine Callable zurück, die dem Funktionsnamen anstelle des kompilierten Funktionskörpers zugewiesen wird, wie es normalerweise der Fall wäre.

Wenn jedoch ein Dekorateur Funktion explizit eine gegeben ist oder mehrere Argumente, wenn es aufgerufen wird, wie folgt aus:

@deco(args) 
    def somefunc(...): pass 

Es wird äquivalent zu:

def somefunc(...): pass 
    somefunc = deco(args)(somefunc) 

Wie Sie sehen können, die Dinge in dieser Der Fall funktioniert etwas anders. Die Decorator-Funktion gibt immer noch eine Callable zurück, nur diesmal , die zu einer "normalen" einzelnen impliziten Argument-Dekorator-Funktion erwartet wird, die dann mit einem Funktionsobjekt aus der folgenden Funktionsdefinition wie zuvor aufgerufen wird.

Wieder – als andere haben – darauf hingewiesen, dies macht Dekorateure ausdrücklich Argumente übergeben, Dekorateur Fabriken in dem Sinne, dass sie konstruieren und ‚normale‘ Dekorateur Funktionen zurück.

In den meisten wenn nicht allen Fällen können Decorators entweder als Funktion oder als Klasse implementiert werden, da beide in Python aufgerufen werden können. Ich persönlich finde Funktionen etwas leichter zu verstehen und werde diesen Ansatz im Folgenden verwenden. Auf der anderen Seite kann der Funktionsansatz schwierig werden, da er oft eine oder mehrere verschachtelte Funktionsdefinitionen beinhaltet.

So können Sie einen Dekorator für die Funktion do() in Ihrem Modul codieren. Im folgenden Code habe ich definiert, was es tut, ist die Argumente der Funktion vor dem Aufruf auszudrucken.

def do(fn, args=tuple(), kwargs={}, priority=0, 
     block=False, timeout=0, callback=None, daemon=False): 
    # show arguments 
    print ('in do(): fn={!r}, args={}, kwargs={}, priority={},\n' 
      '   block={}, timeout={}, callback={}, daemon={}' 
      .format(fn.__name__, args, kwargs, priority, 
        block, timeout, callback, daemon)) 
    # and call function 'fn' with its arguments 
    print (' calling {}({}, {})'.format(
       fn.__name__, 
       ', '.join(map(str, args)) if args else '', 
       ', '.join('{}={}'.format(k, v) for k,v in kwargs.items()) 
        if kwargs else '') 
     ) 
    fn(*args, **kwargs) 

def do_decorator(**do_kwargs): 
    def decorator(fn): 
     def decorated(*args, **kwargs): 
      do(fn, args, kwargs, **do_kwargs) 
     return decorated 
    return decorator 

@do_decorator(priority=2) 
def decoratedTask(arg, dic=42): 
    print 'in decoratedTask(): arg={}, dic={}'.format(arg, dic) 

decoratedTask(72, dic=3) 

Ausgang:

in do(): fn='decoratedTask', args=(72,), kwargs={'dic': 42}, priority=2, 
     block=False, timeout=0, callback=None, daemon=False 
    calling decoratedTask(72, dic=3) 
in decoratedTask(): arg=72, dic=3 

Hier ist ein Blow-by-Blow-Konto, wie dieses komplizierte suchen Zeug funktioniert:

Die äußere Dekorateur Funktion do_decorator(), definiert eine andere Innendekorateur-Funktion, die es zurückgibt , hier kreativ genannt decorator.

Was decorator tut, ist zu definieren, was zu Funktionen geschieht, die in einem einfachen ‚keine Argumente‘ -Szenario — eingerichtet, die hier definiert, und noch eine weitere – aber letzte – verschachtelte Funktion decorated genannt Rückkehr, die nur die do() Funktion des Moduls aufruft und übergibt es sowohl die Argumente, wenn überhaupt von der Stelle des Aufrufs, zusammen mit denen, die für die do() Funktion vorgesehen sind.

Dieser Anwendungsfall wird durch die Tatsache kompliziert, dass sowohl der äußere Dekorator als auch die zu dekorierenden Funktionen Schlüsselwortargumente haben. Besondere Sorgfalt ist erforderlich, um sicherzustellen, dass die Namen der Schlüsselwörter für jeden Wert eindeutig sind, damit sie nicht in Konflikt stehen (und dass der veränderbare kwargs-Standardwert für den Argumentwert nicht unbeabsichtigt durch eine Funktion in der Funktion do() geändert wird).

0

Ich mag @Lennart Regebro, @katrielalex und @martineau oben Antworten, aber auf die Gefahr sehr kitschig klingen, ich werde riskieren eine Geschichte basiert Beispiel zu schreiben.

In dieser zweiteiligen Geschichte, können wir Lehrer haben die Studenten aus Erfahrung lehren.

def self_taught_teacher(fn): 
    ''' I teach students, because I had no teacher to guide me.''' 
    def a_student(*real_life_issues, **details_about_my_world): 
     ''' I'm a student who has been trained by a teacher 
     who has taught me about the problems I may encounter in real life. 

     Thanks to my teacher for giving me extra knowledge.  
     ''' 
     print 'I have to remember what my teacher taught me.' 
     my_answer = fn(*real_life_issues, **details_about_my_world) 
     print 'Ah yes, I made the right decision.' 
     # 
     return my_answer 
    # 
    return a_student 

@self_taught_teacher 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

Mal sehen, was der Student kennt ...

>>> answer = student_named_Lisa_practicing_maths(10, 20, height=3) 
I have to remember what my teacher taught me. 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> answer 
600 

Sehr gut. Im zweiten Teil der Geschichte, stellen wir einen Professor vor, der andere lehrt, Lehrer zu werden. Diese Lehrer dann teilen, was sie mit ihren Studenten gelernt haben.

def professor_who_trains_teachers(*subjects, **to_train_teachers): 
    '''I am a profeseur. I help train teachers. ''' 
    # 
    def a_teacher_who_gets_trained(fn): 
     ''' I learn subjects I should teach to my students.''' 
     knowledge = [s for s in subjects] 
     # 
     def a_student(*real_life_issues, **details_about_my_world): 
      ''' I'm a student who has been trained by a teacher 
      who has taught me about the problems I may encounter in real life. 

      Thanks to my teacher for giving me extra knowledge.  
      ''' 
      print '(I know %s that i learned from my teacher,...)' % \ 
        [information for information in knowledge] 
      my_answer = fn(*real_life_issues, **details_about_my_world) 
      print 'Ah yes, I made the right decision.' 
      # 
      return my_answer 
     # 
     return a_student 
     # 
     # 
    return a_teacher_who_gets_trained 

So können wir einen Lehrer ausbilden und lassen sie einen Schüler lehren ...

>>> teacher1 = professor_who_trains_teachers('math','science') 
>>> teacher1 
<function a_teacher_who_gets_trained at 0x104a7f500> 
>>> teacher1.__name__ 
'a_teacher_who_gets_trained' 
>>> 

@teacher1 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

Dass Schüler ihre Mathematik kennt ..

>>> answer = student_named_Lisa_practicing_maths(20, 10, 2) 
(I know ['math', 'science'] that i learned from my teacher,...) 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> 
>>> answer 
400 

Und wir können die Lehrer schaffen aus - Auch richtig.

@professor_who_trains_teachers('math', 'science', remember='patience') 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

Auch die neuen Schüler ihre Mathe tun können ...

>>> answer = student_named_Lisa_practicing_maths(10, 20, height=3) 
(I know ['math', 'science'] that i learned from my teacher,...) 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> 
>>> answer 
600 
Verwandte Themen