如何在每个对象方法之后获得一个名为(装饰器?)的方法

How to get a method called (decorator?) after every object method

这是一个类似于 的问题,但 python

假设我有一个爬虫 class,它具有一些属性(例如 self.db),其中一个 crawl_1(self, *args, **kwargs) 和另一个 save_to_db(self, *args, **kwargs) 将爬行结果保存到数据库( self.db).

我想以某种方式在每次 crawl_1, crawl_2, etc. 调用后得到 save_to_db 运行。我试过将其作为 "global" util 装饰器,但我不喜欢这个结果,因为它涉及将 self 作为参数传递。

像这样:

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

如果您想在所有 crawl_* 方法之后隐式 运行 一个方法,最简单的解决方案可能是设置一个 metaclass 以编程方式包装方法你。从这个开始,一个简单的包装函数:

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 后。现在,我们设置一个 metaclass 这将以编程方式将其应用于特定方法:

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)

这将遍历包装 class 中的方法,寻找 以 crawl_ 开头并用我们的方法包装它们的方法名称 装饰函数。

最后,包装的 class 本身,它将 Wrapper 声明为 元class:

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 之后)。

以上在Python2中有效。在Python3中,替换为:

class Wrapped (object):
    __metaclass__ = Wrapper

有:

class Wrapped (object, metaclass=Wrapper):

Python中的装饰器看起来像这样,它是一种方法,它以单个方法作为参数并返回另一个包装器方法,而不是装饰器方法。通常包装器 "wraps" 装饰方法,即调用它 before/after 执行一些其他操作。

示例:

# 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