【问题标题】:How to wrap python instance methods at runtime如何在运行时包装python实例方法
【发布时间】:2013-01-17 17:31:47
【问题描述】:

我想在某个类中进行调用时记录调试信息。记录的数据是:

  • 函数名
  • 函数调用位置的堆栈跟踪
  • 函数执行的时间
  • args 和 kwargs 传递给函数

我希望包装器相当通用。同时,包装应该在运行时进行。我制作了以下包装类来记录信息并将调用委托给原始实例。

import datetime
import traceback
from functools import partial

from logging_module import db_log


class BaseWrapper(object):
    def __init__(self, item):
        self._item = item

    def __getattr__(self, attr):
        return getattr(self._item, attr)


class DBLogWrapper(BaseWrapper):

    @staticmethod
    def _time_method(method):
        name = "{0}.{1}.{2}".format(
            method.im_class.__module__,
            method.im_class.__name__,
            method.__name__
        )

        def timed_method(self, *args, **kwargs):
            begin = datetime.datetime.now()
            return_val = method.im_func(self, *args, **kwargs)
            end = datetime.datetime.now()

            trace = traceback.format_stack()

            db_log(
                name,
                begin,
                end,
                info={
                    'args': args,
                    'kwargs': kwargs,
                    'trace': trace
                }
            )
            return return_val

        return timed_method

    def __init__(self, item, methods):
        super(DBLogWrapper, self).__init__(item)
        for method in methods:
            class_method = getattr(item, method)
            wrapped_method = DBLogWrapper._time_method(class_method)
            wrapped_method = partial(wrapped_method, self._item)
            setattr(self, method, wrapped_method)

示例用法:

class MyClass(object):

    def hello(self, greeting):
        print greeting

    def goodbye(self):
        print 'Good Bye'

a = MyClass()

if DEBUG:
    a = DBLogWrapper(a, ['hello'])

a.hello()
a.goodbye()

在这种情况下,对hello 的调用将被记录,但对goodbye 的调用不会。

但是,对于一个看起来应该很简单的任务来说,这似乎有点过头了。我正在寻找有关如何改进上述代码或完全不同的方法的建议。

【问题讨论】:

  • 这有什么过分的?
  • 整个解决方案对我来说似乎很复杂。在委托包装器中使用 partial 来使实例方法按预期工作。只是感觉有点太老套了
  • 您为什么不简单地编写一个装饰器并将其应用于您想要跟踪的方法?我认为它更简单,更清晰。
  • 好吧,它本质上已经是一个装饰器了。但是,出于几个原因,我不想仅仅装饰类方法。首先,因为我无法访问我想要跟踪的方法。它们在第 3 方模块中定义。其次,因为我希望在运行时根据某些标志应用这些装饰器。我不想一直打开它们。

标签: python logging dynamic metaprogramming wrapper


【解决方案1】:

你做的太多了。你根本不需要partial。只定义timed_method,不带self参数,直接调用method

import datetime
import traceback
from functools import partial

def db_log(*args, **kwargs): print args, kwargs # Mock


class BaseWrapper(object):
    def __init__(self, instance):
        self._instance = instance

    def __getattr__(self, attr):
        return getattr(self._instance, attr)


class DBLogWrapper(BaseWrapper):

    @staticmethod
    def _time_method(method):
        name = "{0}.{1}.{2}".format(
            method.im_class.__module__,
            method.im_class.__name__,
            method.__name__
        )

        def timed_method(*args, **kwargs):
            begin = datetime.datetime.now()
            return_val = method(*args, **kwargs)
            end = datetime.datetime.now()

            trace = traceback.format_stack()

            db_log(
                name,
                begin,
                end,
                info={
                    'args': args,
                    'kwargs': kwargs,
                    'trace': trace
                }
            )
            return return_val

        return timed_method

    def __init__(self, instance, methods):
        super(DBLogWrapper, self).__init__(instance)
        for method in methods:
            class_method = getattr(instance, method)
            wrapped_method = DBLogWrapper._time_method(class_method)
            setattr(self, method, wrapped_method)

输出:

>>> a = MyClass()
>>> a = prova.DBLogWrapper(a, ['hello'])
>>> a.hello()
A
('__main__.MyClass.hello', datetime.datetime(2013, 1, 17, 20, 48, 26, 478023), datetime.datetime(2013, 1, 17, 20, 48, 26, 478071)) {'info': {'args': (), 'trace': ['  File "<stdin>", line 1, in <module>\n', '  File "prova.py", line 31, in timed_method\n    trace = traceback.format_stack()\n'], 'kwargs': {}}}
>>> a.goodbye()
B

无论如何,也许你可以使用一些__getattr__ 魔法,例如:

class DBLogWrapper2(BaseWrapper):

    def __init__(self, instance, methods):
        super(DBLogWrapper, self).__init__(instance)

        self._methods = methods

    def __getattr__(self, attr):
        if attr not in methods:
            return getattr(self._instance, attr)

        def f(*args, **kwargs):
            return self.timed_method(getattr(self._item, attr),
                                     *args, **kwargs)
        return f

    def timed_method(method, *args, **kwargs):
        begin = datetime.datetime.now()
        return_val = method(*args, **kwargs)
        end = datetime.datetime.now()

        trace = traceback.format_stack()

        db_log(name,
            begin,
            end,
            info={
                'args': args,
                'kwargs': kwargs,
                'trace': trace
            }
        )
        return return_val

【讨论】:

  • 啊啊方法已经绑定到item了!太棒了,这清理了一点。至于 getattr 魔法,我想尽量避免在每次调用时创建定时方法。我想我可以让它变得懒惰并在创建后将包装的方法存储在 self.__dict__ 中。无论哪种方式,这都有很大帮助。谢谢
  • @Drover 没错。方法是使用 getter 定义的,因此,如果您从类中访问它们,它们不会被绑定,但通过实例访问它们会限制 self 参数。阅读this 以获得更好的解释。
猜你喜欢
  • 1970-01-01
  • 2018-10-11
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 2010-12-12
  • 2014-12-31
  • 1970-01-01
  • 2011-02-02
相关资源
最近更新 更多