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