【问题标题】:Python - Print method name whenever the methods of a class is calledPython - 每当调用类的方法时打印方法名称
【发布时间】:2015-11-21 13:40:40
【问题描述】:

每次调用特定类的方法时,我都需要执行某些操作(例如记录方法名称)。这如何在 Python 中以通用方式实现?

【问题讨论】:

标签: python class methods


【解决方案1】:

在元类中装饰可调用属性:

from functools import wraps

def _log_method(val):
    @wraps(val)
    def wrapper(*a, **ka):
        print(val.__name__, 'is called')
        val(*a, **ka)
    return wrapper

class LogMethodCalls(type):
    def __new__(cls, cls_name, bases, attrs):
        for name, attr in attrs.items():
            if callable(attr):
                attrs[name] = _log_method(attr)
        return type.__new__(cls, cls_name, bases, attrs)

class Foo(metaclass=LogMethodCalls):
    def my_method(self):
        pass

Foo().my_method() # my_method is called

警告:此代码仅适用于实例方法,使用@classmethod@staticmethod 装饰的方法将不会被记录(因为classmethod 和@ 987654327@ 对象是不可可调用的 - 它们只是非数据描述符)。


以下也适用于类方法和静态方法:

from functools import wraps

def _log_method(val):
    @wraps(val)
    def wrapper(*a, **ka):
        print('calling', val.__name__)
        val(*a, **ka)
    return wrapper

class LogMethodCalls(type):
    def __new__(cls, cls_name, bases, attrs):
        for name, attr in attrs.items():
            if callable(attr):
                attrs[name] = _log_method(attr)
            elif isinstance(attr, (classmethod, staticmethod)):
                attrs[name] = type(attr)(_log_method(attr.__func__))
        return type.__new__(cls, cls_name, bases, attrs)

class Foo(metaclass=LogMethodCalls):

    def my_instance_method(self):
        pass

    @classmethod
    def my_class_method(cls):
        pass

    @staticmethod
    def my_static_method():
        pass

Foo().my_instance_method() # calling my_instance_method
Foo.my_class_method() # calling my_class_method
Foo.my_static_method() # calling my_static_method

它们具有__func__ 属性,我们可以对其进行装饰。


请注意,您需要使用

class Foo(object):
    __metaclass__ = LogMethodCalls

在 Python 2 中。

【讨论】:

  • 感谢超级有用!编辑 val(*a, **ka)return val(*a, **ka) 以避免从返回不同内容的函数返回 None
【解决方案2】:

取自this answer。您可以使用inspect 模块查看函数名称的堆栈,以创建一个简单的日志记录函数。看起来有点像 hack,但我想它回答了这个问题。

import inspect

def log_call():
    print(inspect.stack()[1][3])

def my_func():
    log_call()
    # do stuff

my_func()

这将打印my_func

【讨论】:

    【解决方案3】:

    你可以实现一个decorator:

    from functools import wraps
    
    def print_function_name(function):
        @wraps(function)
        def do_it():
            print function.__name__
            function()
        return do_it
    

    用法:

    class MyClass(object):
        @print_function_name
        def some_function(self):
            pass
    

    例如:

    >>> my_object = MyClass()
    >>> my_object.some_function()
    some_function
    

    使用functools.wraps 确保函数保留其文档和名称,而不是变成do_it

    【讨论】:

    • 谢谢 - 您的回答比使用 functools.wrap - +1 的其他人简单得多!
    【解决方案4】:

    取自https://stackoverflow.com/a/5103895/5270581:
    object 类的以下方法在每次访问对象的属性时调用,包括方法调用:

    __get_attribute__

    所以我建议通过简单地在内部添加对日志记录函数的调用来覆盖它。
    代码示例见https://stackoverflow.com/a/5103895/5270581(转到最后一个答案)。

    【讨论】:

    • 覆盖__get_attribute__ 绝对不是一个好主意,因为你可以这样做。
    • @brunodesthuilliers:你能解释一下为什么这不是一个好主意吗? (或添加解释链接)
    • 这不是一个好主意的两个主要原因(至少当有其他解决方案时)是 1. 它很棘手; 2. 你会在每个属性访问上获得性能损失。事实上,object.__getattribute__ 是属性解析的默认实现(包括处理描述符协议以及根据 mro 在类和父级上查找名称),并且是在 C 中实现的。因此,有可能覆盖它很方便,但作为有了 Python 中的一些特性,最明智的做法是仅在没有更好的方法时使用它。
    【解决方案5】:

    这是我在这篇帖子here的回答

    它可以通过许多不同的方式完成。我将展示如何通过元类类装饰器继承来实现

    通过更改元类

    import functools
    
    
    class Logger(type):
        @staticmethod
        def _decorator(fun):
            @functools.wraps(fun)
            def wrapper(*args, **kwargs):
                print(fun.__name__, args, kwargs)
                return fun(*args, **kwargs)
            return wrapper
    
        def __new__(mcs, name, bases, attrs):
            for key in attrs.keys():
                if callable(attrs[key]):
                    # if attrs[key] is callable, then we can easily wrap it with decorator
                    # and substitute in the future attrs
                    # only for extra clarity (though it is wider type than function)
                    fun = attrs[key]
                    attrs[key] = Logger._decorator(fun)
            # and then invoke __new__ in type metaclass
            return super().__new__(mcs, name, bases, attrs)
    
    
    class A(metaclass=Logger):
        def __init__(self):
            self.some_val = "some_val"
    
        def method_first(self, a, b):
            print(a, self.some_val)
    
        def another_method(self, c):
            print(c)
    
        @staticmethod
        def static_method(d):
            print(d)
    
    
    b = A()
    # __init__ (<__main__.A object at 0x7f852a52a2b0>,) {}
    
    b.method_first(5, b="Here should be 5")
    # method_first (<__main__.A object at 0x7f852a52a2b0>, 5) {'b': 'Here should be 5'}
    # 5 some_val
    b.method_first(6, b="Here should be 6")
    # method_first (<__main__.A object at 0x7f852a52a2b0>, 6) {'b': 'Here should be 6'}
    # 6 some_val
    b.another_method(7)
    # another_method (<__main__.A object at 0x7f852a52a2b0>, 7) {}
    # 7
    b.static_method(7)
    # 7
    

    另外,将展示如何在不改变类元信息的情况下实现它的两种方法(通过类装饰器类继承)。第一种通过类装饰器 put_decorator_on_all_methods 接受装饰器来包装类的所有成员可调用对象。

    def logger(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f.__name__, args, kwargs)
            return f(*args, **kwargs)
    
        return wrapper
    
    
    def put_decorator_on_all_methods(decorator, cls=None):
        if cls is None:
            return lambda cls: put_decorator_on_all_methods(decorator, cls)
    
        class Decoratable(cls):
            def __init__(self, *args, **kargs):
                super().__init__(*args, **kargs)
    
            def __getattribute__(self, item):
                value = object.__getattribute__(self, item)
                if callable(value):
                    return decorator(value)
                return value
    
        return Decoratable
    
    
    @put_decorator_on_all_methods(logger)
    class A:
        def method(self, a, b):
            print(a)
    
        def another_method(self, c):
            print(c)
    
        @staticmethod
        def static_method(d):
            print(d)
    
    
    b = A()
    b.method(5, b="Here should be 5")
    # >>> method (5,) {'b': 'Here should be 5'}
    # >>> 5
    b.method(6, b="Here should be 6")
    # >>> method (6,) {'b': 'Here should be 6'}
    # >>> 6
    b.another_method(7)
    # >>> another_method (7,) {}
    # >>> 7
    b.static_method(8)
    # >>> static_method (8,) {}
    # >>> 8
    

    而且,最近,我遇到了同样的问题,但我无法将装饰器放在类上或以任何其他方式更改它,除非我被允许添加这样的行为通过继承 仅限(如果您可以随意更改代码库,我不确定这是否是最佳选择)。

    这里的类Logger 强制子类的所有可调用成员写入有关其调用的信息,请参见下面的代码。

    class Logger:
    
        def _decorator(self, f):
            @functools.wraps(f)
            def wrapper(*args, **kwargs):
                print(f.__name__, args, kwargs)
                return f(*args, **kwargs)
    
            return wrapper
    
        def __getattribute__(self, item):
            value = object.__getattribute__(self, item)
            if callable(value):
                decorator = object.__getattribute__(self, '_decorator')
                return decorator(value)
            return value
    
    
    class A(Logger):
        def method(self, a, b):
            print(a)
    
        def another_method(self, c):
            print(c)
    
        @staticmethod
        def static_method(d):
            print(d)
    
    b = A()
    b.method(5, b="Here should be 5")
    # >>> method (5,) {'b': 'Here should be 5'}
    # >>> 5
    b.method(6, b="Here should be 6")
    # >>> method (6,) {'b': 'Here should be 6'}
    # >>> 6
    b.another_method(7)
    # >>> another_method (7,) {}
    # >>> 7
    b.static_method(7)
    # >>> static_method (7,) {}
    # >>> 7
    

    或者更抽象地说,你可以基于一些装饰器来实例化基类。

    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f.__name__, args, kwargs)
            return f(*args, **kwargs)
        return wrapper
    
    
    class Decoratable:
        def __init__(self, dec):
            self._decorator = dec
    
        def __getattribute__(self, item):
            value = object.__getattribute__(self, item)
            if callable(value):
                decorator = object.__getattribute__(self, '_decorator')
                return decorator(value)
            return value
    
    
    class A(Decoratable):
        def __init__(self, dec):
            super().__init__(dec)
    
        def method(self, a, b):
            print(a)
    
        def another_method(self, c):
            print(c)
    
        @staticmethod
        def static_method(d):
            print(d)
    
    b = A(decorator)
    b.method(5, b="Here should be 5")
    # >>> method (5,) {'b': 'Here should be 5'}
    # >>> 5
    b.method(6, b="Here should be 6")
    # >>> method (6,) {'b': 'Here should be 6'}
    # >>> 6
    b.another_method(7)
    # >>> another_method (7,) {}
    # >>> 7
    b.static_method(7)
    # >>> static_method (7,) {}
    # >>> 7
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-02
      • 2017-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-28
      • 2022-10-06
      相关资源
      最近更新 更多