如何在运行时包装python实例方法

时间:2013-01-17 17:31:47

标签: python logging dynamic metaprogramming wrapper

我希望在某些类中调用时记录调试信息。记录的数据是:

  • 功能名称
  • 调用函数的堆栈跟踪
  • 执行该功能的时间
  • 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

然而,对于一项似乎应该简单的任务而言,这似乎有些过分。我正在寻找有关如何改进上述代码的建议或完全不同的方法。

1 个答案:

答案 0 :(得分:3)

你做得太多了。您根本不需要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