2

Dies ist eine Frage ähnlich wie How to call a method implicitly after every method call? aber für PythonWie ein Verfahren erhalten genannt (Dekorateur?) Nach jeder Objektmethode

sagt, dass ich mit einigen Attributen eine Crawler-Klasse (zB self.db) mit einem crawl_1(self, *args, **kwargs) und ein anderer save_to_db(self, *args, **kwargs), die die Crawling Ergebnisse in eine Datenbank speichert (self.db).

ich irgendwie save_to_db Lauf nach jedem crawl_1, crawl_2, etc. Anruf haben will. ich habe versucht, dies als „global“ util Dekorateur machen, aber ich mag es nicht die Ergebnis, da es um self als Argument herumgeht

+0

Was wollen Sie passieren wenn 'crawl_1 'löst eine Ausnahme aus? – RoadieRich

Antwort

5

Wenn Sie eine Methode wollen, nachdem alle Ihre crawl_* Methoden implizit ausgeführt werden, ist die einfachste Lösung sein kann Richten Sie eine Metaklasse ein, die die Methoden für Sie programmatisch umschließt. Beginnen Sie mit diesem, eine einfache Wrapper-Funktion:

import functools 

def wrapit(func): 
    @functools.wraps(func) 
    def _(self, *args, **kwargs): 
     func(self, *args, **kwargs) 
     self.save_to_db() 

    return _ 

, dass ein Grund Dekorateur ist die func Wraps, ruft self.save_to_db() nach func aufrufen. Nun haben wir eine Metaklasse up, die diese spezielle Methoden programmatisch gelten:

class Wrapper (type): 
    def __new__(mcls, name, bases, nmspc): 
     for attrname, attrval in nmspc.items(): 
      if callable(attrval) and attrname.startswith('crawl_'): 
       nmspc[attrname] = wrapit(attrval) 

     return super(Wrapper, mcls).__new__(mcls, name, bases, nmspc) 

Diese über die Methoden in der umwickelten Klasse iterieren werden, für Methodennamen suchen, der mit crawl_ und Verpackung sie mit unserer starten Decorator-Funktion.

schließlich die verpackte Klasse selbst, die Wrapper als metaclass erklärt:

class Wrapped (object): 
    __metaclass__ = Wrapper 

    def crawl_1(self): 
     print 'this is crawl 1' 

    def crawl_2(self): 
     print 'this is crawl 2' 

    def this_is_not_wrapped(self): 
     print 'this is not wrapped' 

    def save_to_db(self): 
     print 'saving to database' 

die oben gegeben, so erhalten wir das folgende Verhalten:

>>> W = Wrapped() 
>>> W.crawl_1() 
this is crawl 1 
saving to database 
>>> W.crawl_2() 
this is crawl 2 
saving to database 
>>> W.this_is_not_wrapped() 
this is not wrapped 
>>> 

Sie können die unsere save_to_database Methode sehen wird nach jeweils von crawl_1 und crawl_2 (aber nicht nach this_is_not_wrapped) aufgerufen.

Die oben genannten Arbeiten in Python 2. In Python 3 replase dies:

class Wrapped (object): 
    __metaclass__ = Wrapper 

mit:

class Wrapped (object, metaclass=Wrapper): 
+0

http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html ist nützlich beim Lesen von Metaklassen. – larsks

+1

['functools.wraps'] (https://docs.python.org/3.5/library/functools.html#functools.wraps) ist eine bequeme Möglichkeit, die Attribute '__name__' und' __doc__' zu setzen. – ChrisP

+0

Guter Vorschlag; Ich habe die Antwort aktualisiert, um 'functools.wraps' zu verwenden. – larsks

0

Etwas wie folgt aus:

from functools import wraps 

def my_decorator(f): 
    @wraps(f) 
    def wrapper(*args, **kwargs): 
     print 'Calling decorated function' 
     res = f(*args, **kwargs) 
     obj = args[0] if len(args) > 0 else None 
     if obj and hasattr(obj, "bar"): 
      obj.bar() 

    return wrapper 

class MyClass(object): 
    @my_decorator 
    def foo(self, *args, **kwargs): 
     print "Calling foo" 

    def bar(self, *args, **kwargs): 
     print "Calling bar" 

@my_decorator 
def example(): 
    print 'Called example function' 

example() 

obj = MyClass() 
obj.foo() 

Es wird Ihnen die folgende Ausgabe geben:

Calling decorated function 
Called example function 
Calling decorated function 
Calling foo 
Calling bar 
+0

'obj = args [0] if len (args)> 0 else Keines sollte' obj = args [0] sein, wenn args else None': ein leeres 'tuple' ist" falsey ", nicht-leeres" truthy "wenn in einem booleschen Kontext ausgewertet. – RoadieRich

0

Ein Dekorateur in Python wie folgt aussieht, es ist ein Verfahren zur Herstellung eine einzige Methode wobei als Argument und Rückgabe einer anderen Wrapper-Methode, die anstelle der dekorierten aufgerufen werden soll. Gewöhnlich "wickelt" der Wrapper das dekorierte Verfahren ein, d. H. Ruft es vor/nach Ausführen einiger anderer Aktionen auf.

Beispiel:

# define a decorator method: 
def save_db_decorator(fn): 

    # The wrapper method which will get called instead of the decorated method: 
    def wrapper(self, *args, **kwargs): 
     fn(self, *args, **kwargs)   # call the decorated method 
     MyTest.save_to_db(self, *args, **kwargs) # call the additional method 

    return wrapper # return the wrapper method 

nun lernen, wie man es benutzt:

class MyTest: 

    # The additional method called by the decorator: 

    def save_to_db(self, *args, **kwargs): 
     print("Saver") 


    # The decorated methods: 

    @save_db_decorator 
    def crawl_1(self, *args, **kwargs): 
     print("Crawler 1") 

    @save_db_decorator 
    def crawl_2(self, *args, **kwargs): 
     print("Crawler 2") 


# Calling the decorated methods: 

my_test = MyTest() 
print("Starting Crawler 1") 
my_test.crawl_1() 
print("Starting Crawler 1") 
my_test.crawl_2() 

Dies würde die Ausgabe wie folgt vor:

Starting Crawler 1 
Crawler 1 
Saver 
Starting Crawler 1 
Crawler 2 
Saver 

See this code running on ideone.com

Verwandte Themen