【问题标题】:Python: Logging all of a class' methods without decorating each onePython:记录一个类的所有方法而不装饰每个方法
【发布时间】:2013-06-06 12:19:17
【问题描述】:

我想记录某些类中的每个方法调用。我本来可以做的

class Class1(object):
    @log
    def method1(self, *args):
        ...
    @log
    def method2(self, *args):
        ...

但是我每个类都有很多方法,我不想单独装饰每一个。目前,我尝试使用带有元类的 hack(覆盖我的日志类'__getattribute__,这样如果我尝试获取一个方法,它将返回一个日志方法):

class LoggedMeta(type):
    def __new__(cls, name, bases, attrs):
        def __getattribute__(self, name_):
            attr = super().__getattribute__(name_)
            if isinstance(attr, (types.MethodType, types.FunctionType)) and not name_.startswith("__"):
                return makeLogged(attr) #This returns a method that first logs the method call, and then calls the original method.
            return attr
        attrs["__getattribute__"] = __getattribute__
    return type.__new__(cls, name, bases, attrs)

class Class1(object):
    __metaclass__ = LoggedMeta
    def method1(self, *args):
        ...

但是,我使用的是 Python 2.X,并且 super() 语法不起作用。在我调用 super 的时候,我没有 __getattribute__ 的类(但我确实有它的类名),所以我不能使用旧的 super 语法 super(Class, Inst)

我之前尝试过使用元类,但覆盖了所有方法而不是 __getattribute__,但我也想记录静态方法调用,它们给我带来了一些麻烦。

我搜索了这类问题,但没有人尝试过以这种方式更改课程。

任何想法或帮助将不胜感激。

编辑: 我的解决方案是这样的(主要取自this 线程):

import inspect, types

CLASS = 0
NORMAL = 1
STATIC = 2

class DecoratedMethod(object):

    def __init__(self, func, type_):
        self.func = func
        self.type = type_

    def __get__(self, obj, cls=None):
        def wrapper(*args, **kwargs):
            print "before"
            if self.type == CLASS:
                #classmethods (unlike normal methods) reach this stage as bound methods, but args still contains the class
                #as a first argument, so we omit it.
                ret = self.func(*(args[1:]), **kwargs)
            else:
                ret = self.func(*args, **kwargs)
            print "after"
            return ret
        for attr in "__module__", "__name__", "__doc__":
            setattr(wrapper, attr, getattr(self.func, attr))
        if self.type == CLASS:
            return types.MethodType(wrapper, cls, type)
        elif self.type == NORMAL:
            return types.MethodType(wrapper, obj, cls) 
        else:
            return wrapper

def decorate_class(cls):
    for name, meth in inspect.getmembers(cls):
        if inspect.ismethod(meth):
            if inspect.isclass(meth.im_self):
                # meth is a classmethod
                setattr(cls, name, DecoratedMethod(meth, CLASS))
            else:
                # meth is a regular method
                setattr(cls, name, DecoratedMethod(meth, NORMAL))
        elif inspect.isfunction(meth):
            # meth is a staticmethod
            setattr(cls, name, DecoratedMethod(meth, STATIC))
    return cls


@decorate_class
class MyClass(object):

    def __init__(self):
        self.a = 10
        print "__init__"

    def foo(self):
        print self.a

    @staticmethod
    def baz():
        print "baz"

    @classmethod
    def bar(cls):
        print "bar"

后来我对其进行了一些清理,但这就是解决方案的本质。我需要类、静态和普通方法之间的这种区别,因为我想拥有

inst = MyClass()
assert type(inst.baz) == types.FunctionType
assert type(inst.foo) == types.MethodType
assert type(inst.bar) == types.MethodType

【问题讨论】:

标签: python class logging


【解决方案1】:

为什么不改变类对象?

您可以使用 dir(MyClass) 遍历类中的方法,并将它们替换为包装版本...类似于:

def logify(klass):
    for member in dir(klass):
        if not callable(getattr(klass, method))
            continue # skip attributes
        setattr(klass, method, log(method))

修补一下这样的东西...应该可以工作...

【讨论】:

  • 如果你有这个return klass,你可以把它用作类装饰器。
  • 不错,@mgilson。我从来没有接触过官方装饰器的东西,因为我只是不经常使用它......
【解决方案2】:

类装饰器可以在这里提供帮助。装饰整个类并将日志记录功能添加到类具有的所有可调用属性中。

【讨论】:

    【解决方案3】:

    如果目标是通过简单地记录调用和响应来使您的代码更易于调试,请查看自动记录模块。只需一个注释 =)

    https://pythonhosted.org/Autologging/examples-traced.html

    pip install Autologging
    

    .

    # my_module.py
    
    from autologging import traced
    
    
    @traced
    class MyClass:
    
       def __init__(self):
          self._value = "ham"
    
       def my_method(self, arg, keyword=None):
          return "%s, %s, and %s" % (arg, self._value, keyword)
    

    .

    >>> import logging, sys
    >>> from autologging import TRACE
    >>> logging.basicConfig(level=TRACE, stream=sys.stdout,
    ...     format="%(levelname)s:%(name)s:%(funcName)s:%(message)s")
    >>> from my_module import MyClass
    >>> my_obj = MyClass()
    TRACE:my_module.MyClass:__init__:CALL *() **{}
    TRACE:my_module.MyClass:__init__:RETURN None
    >>> my_obj.my_method("spam", keyword="eggs")
    TRACE:my_module.MyClass:my_method:CALL *('spam',) **{'keyword': 'eggs'}
    TRACE:my_module.MyClass:my_method:RETURN 'spam, ham, and eggs'
    'spam, ham, and eggs'
    

    【讨论】:

    • TBH,我真的不记得目标是什么了,但这看起来正好解决了这个问题。
    【解决方案4】:

    我建议从this SO post 获取 for_all_methods 装饰器,那么你的代码就是

    @for_all_methods(log)
    class Class1():
       def method1(self): pass
       ...
    

    【讨论】:

      猜你喜欢
      • 2021-12-26
      • 1970-01-01
      • 2021-10-23
      • 2015-03-07
      • 1970-01-01
      • 1970-01-01
      • 2019-11-02
      • 2011-10-05
      • 2015-12-08
      相关资源
      最近更新 更多