2

這類似於How to call a method implicitly after every method call?但蟒蛇如何對每個對象的方法

問題

說我有一些屬性(例如self.db)與crawl_1(self, *args, **kwargs)和爬蟲類之後得到一個名爲(裝飾?)方法另一個save_to_db(self, *args, **kwargs)節省了爬行結果到數據庫(self.db)

我想以某種方式有save_to_db運行間隔crawl_1, crawl_2, etc.電話後,我試圖使這是一個「全球性」 util的裝飾,但我不喜歡因爲它涉及傳遞self作爲參數。

+0

你想要什麼發生如果'crawl_1 '拋出異常? – RoadieRich

回答

5

如果你想暗示運行後,所有的crawl_*方法的方法,最簡單的解決方案可能是設置一個元類,以編程方式爲你包裝這些方法。開始這個問題,一個簡單的包裝功能:

import functools 

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

    return _ 

這是一個包裝func一個基本的裝飾,呼籲 self.save_to_db()調用func後。現在,我們成立了元類 將編程應用此具體方法:

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) 

這將遍歷在包裝類中的方法,尋找與crawl_開始 方法名,並與我們的 包裝他們裝飾者功能。

最後,包裹類本身,它聲明Wrapper作爲 元類:

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' 

鑑於上述情況,我們得到以下行爲:

>>> 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 
>>> 

你可以看到我們的save_to_database方法在crawl_1crawl_2(但不是在this_is_not_wrapped之後)的 之後被調用。

在Python 2.上述作品在Python 3,replase這樣:

class Wrapped (object): 
    __metaclass__ = Wrapper 

隨着:

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

http://python-3-patterns-idioms-test.readthedocs.org/en/latest/Metaprogramming.html對元類有用。 – larsks

+1

['functools.wraps'](https://docs.python.org/3.5/library/functools.html#functools.wraps)是設置'__name__'和'__doc__'屬性的簡便方法。 – ChrisP

+0

好的建議;我更新了使用'functools.wraps'的答案。 – larsks

0

事情是這樣的:

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() 

它會給你以下的輸出:

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

'obj = args [0] if len(args)> 0否則None'應該是'obj = args [0] if args else None':空'tuple'是「falsey」,非空時「truthy」when在布爾上下文中評估。 – RoadieRich

0

Python中的裝飾看起來像這樣,這是採用單個的方法的方法參數並返回另一個應該被調用的包裝方法,而不是裝飾的方法。通常,包裝器會「包裝」裝飾的方法,即在執行其他操作之前/之後調用它。

例子:

# 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 

現在學習如何使用它:

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() 

這將輸出如下:

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

See this code running on ideone.com

相關問題