【问题标题】:How to get a method called (decorator?) after every object method如何在每个对象方法之后获取一个称为(装饰器?)的方法
【发布时间】:2026-02-10 10:30:01
【问题描述】:

这是一个类似于How to call a method implicitly after every method call?但针对python的问题

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

我想以某种方式让save_to_db 在每次crawl_1, crawl_2, etc. 调用之后运行。我尝试将其作为“全局”实用程序装饰器,但我不喜欢结果,因为它涉及传递 self 作为参数。

【问题讨论】:

  • 如果crawl_1 抛出异常,你希望发生什么?

标签: python python-3.x metaprogramming decorator python-decorators


【解决方案1】:

如果您想在所有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 中,替换为:

class Wrapped (object):
    __metaclass__ = Wrapper

与:

class Wrapped (object, metaclass=Wrapper):

【讨论】:

【解决方案2】:

类似这样的:

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

【讨论】:

  • obj = args[0] if len(args) > 0 else None 应该是 obj = args[0] if args else None:一个空的 tuple 在布尔上下文中评估时是“假”,非空的“真”。
【解决方案3】:

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

【讨论】: