71

我真的不需要这样做,但只是想知道,有没有一种方法可以将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式声明它。

我想它会成为一种方面,而不是装饰器,它确实感觉有点奇怪,但考虑到时间或身份验证之类的东西会很整洁。

4

9 回答 9

41

执行此操作或对类定义进行其他修改的最简洁方法是定义元类。

或者,只需在类定义的末尾应用您的装饰器inspect

import inspect

class Something:
    def foo(self): 
        pass

for name, fn in inspect.getmembers(Something, inspect.isfunction):
    setattr(Something, name, decorator(fn))

当然,在实践中,您会希望更有选择性地应用您的装饰器。一旦你想装饰除一种方法之外的所有方法,你会发现以传统方式使用装饰器语法更容易、更灵活。

于 2010-08-12T12:59:54.517 回答
36

每次你想改变类定义时,你可以使用类装饰器或元类。例如使用元类

import types

class DecoMeta(type):
   def __new__(cls, name, bases, attrs):

      for attr_name, attr_value in attrs.iteritems():
         if isinstance(attr_value, types.FunctionType):
            attrs[attr_name] = cls.deco(attr_value)

      return super(DecoMeta, cls).__new__(cls, name, bases, attrs)

   @classmethod
   def deco(cls, func):
      def wrapper(*args, **kwargs):
         print "before",func.func_name
         result = func(*args, **kwargs)
         print "after",func.func_name
         return result
      return wrapper

class MyKlass(object):
   __metaclass__ = DecoMeta

   def func1(self): 
      pass

MyKlass().func1()

输出:

before func1
after func1

注意:不会修饰staticmethod和classmethod

于 2010-08-12T13:54:47.343 回答
7

Python 3 的更新:

import types


class DecoMeta(type):
    def __new__(cls, name, bases, attrs):

        for attr_name, attr_value in attrs.items():
            if isinstance(attr_value, types.FunctionType):
                attrs[attr_name] = cls.deco(attr_value)

        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def deco(cls, func):
        def wrapper(*args, **kwargs):
            print("before",func.__name__)
            result = func(*args, **kwargs)
            print("after",func.__name__)
            return result
        return wrapper

(感谢邓肯)

于 2019-07-05T08:48:44.410 回答
7

以下代码适用于 python2.x 和 3.x

import inspect

def decorator_for_func(orig_func):
    def decorator(*args, **kwargs):
         print("Decorating wrapper called for method %s" % orig_func.__name__)
         result = orig_func(*args, **kwargs)
         return result
    return decorator

def decorator_for_class(cls):
    for name, method in inspect.getmembers(cls):
        if (not inspect.ismethod(method) and not inspect.isfunction(method)) or inspect.isbuiltin(method):
            continue
        print("Decorating function %s" % name)
        setattr(cls, name, decorator_for_func(method))
    return cls

@decorator_for_class
class decorated_class:
     def method1(self, arg, **kwargs):
         print("Method 1 called with arg %s" % arg)
     def method2(self, arg):
         print("Method 2 called with arg %s" % arg)


d=decorated_class()
d.method1(1, a=10)
d.method2(2)
于 2019-08-06T02:35:06.790 回答
3

对于类似的问题,我将在这里重复我的回答

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

通过改变元类

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
于 2019-11-01T09:30:21.913 回答
2

当然,当您想修改 python 创建对象的方式时,元类是最 Pythonic 的方式。这可以通过覆盖__new__类的方法来完成。但是我想提一下围绕这个问题(特别是对于 python 3.X)的一些要点:

  1. types.FunctionType不保护特殊方法不被修饰,因为它们是函数类型。作为一种更通用的方式,您可以只装饰其名称不是以双下划线 ( __) 开头的对象。此方法的另一个好处是它还涵盖了存在于命名空间中的那些对象,这些对象以 , 等开头但__不是函数。__qualname____module__
  2. 的标头中的namespace参数__new__不包含__init__. 原因是在(初始化)__new__之前执行。__init__

  3. 没有必要使用 aclassmethod作为装饰器,因为大多数时候你从另一个模块导入你的装饰器。

  4. 如果您的类包含一个全局项(在 之外__init__),用于拒绝被装饰,同时检查名称是否不是以您开头的,__您可以检查类型types.FunctionType以确保您没有装饰非功能对象。

这是您可以使用的示例 metacals:

class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # if your decorator is a class method of the metaclass  use
        # `my_decorator = cls.my_decorator` in order to invoke the decorator.
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)

演示:

def my_decorator(func):
        def wrapper(self, arg):
            # You can also use *args instead of (self, arg) and pass the *args
            # to the function in following call.
            return "the value {} gets modified!!".format(func(self, arg))
        return wrapper


class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # my_decorator = cls.my_decorator (if the decorator is a classmethod)
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MyClass(metaclass=TheMeta):
    # a = 10
    def __init__(self, *args, **kwargs):
        self.item = args[0]
        self.value = kwargs['value']

    def __getattr__(self, attr):
        return "This class hasn't provide the attribute {}.".format(attr)

    def myfunction_1(self, arg):
        return arg ** 2

    def myfunction_2(self, arg):
        return arg ** 3

myinstance = MyClass(1, 2, value=100)
print(myinstance.myfunction_1(5))
print(myinstance.myfunction_2(2))
print(myinstance.item)
print(myinstance.p)

输出:

the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.

要检查上述注释中的第三项,您可以取消注释该行a = 10并执行并查看结果,然后按如下方式print(myinstance.a)更改字典理解,然后再次查看结果:__new__

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
             else my_decorator(v) for k, v in namespace.items()}
于 2017-04-16T07:40:20.273 回答
0

在某些情况下,您可能还想做另一件稍微类似的事情。有时您想触发附件以进行调试,而不是针对所有类,而是针对对象的每个方法,您可能希望记录它正在做什么。

def start_debugging():
        import functools
        import datetime
        filename = "debug-{date:%Y-%m-%d_%H_%M_%S}.txt".format(date=datetime.datetime.now())
        debug_file = open(filename, "a")
        debug_file.write("\nDebug.\n")

        def debug(func):
            @functools.wraps(func)
            def wrapper_debug(*args, **kwargs):
                args_repr = [repr(a) for a in args]  # 1
                kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
                signature = ", ".join(args_repr + kwargs_repr)  # 3
                debug_file.write(f"Calling {func.__name__}({signature})\n")
                value = func(*args, **kwargs)
                debug_file.write(f"{func.__name__!r} returned {value!r}\n")  # 4
                debug_file.flush()
                return value
            return wrapper_debug

        for obj in (self):
            for attr in dir(obj):
                if attr.startswith('_'):
                    continue
                fn = getattr(obj, attr)
                if not isinstance(fn, types.FunctionType) and \
                        not isinstance(fn, types.MethodType):
                    continue
                setattr(obj, attr, debug(fn))

这个函数会遍历一些对象(目前只有 self),并用调试装饰器替换所有不以 _ 开头的函数和方法。

上面没有提到用于仅迭代 dir(self) 的方法,但完全有效。并且可以从对象外部调用,并且可以任意调用。

于 2020-02-05T03:10:03.923 回答
0

在 Python 3 中,您还可以编写一个简单的函数,将装饰器覆盖/应用到某些方法,如下所示:

from functools import wraps
from types import MethodType

def logged(func):
   @wraps(func)
   def wrapper(*args, **kwargs):
      res = func(*args, **kwargs)
      print("logging:", func.__name__, res)
      return res
   return wrapper

class Test:
   def foo(self):
      return 42
   ...

def aspectize(cls, decorator):
   for name, func in cls.__dict__.items():
      if not name.startswith("__"):
         setattr(cls, name, MethodType(decorator(func), cls))  # MethodType is key

aspectize(Test, logged)
t = Test()
t.foo()  # printing "logging: foo 42"; returning 42
于 2020-12-02T22:08:44.437 回答
-1

您可以覆盖该__getattr__方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。你可能想做这样的事情:

class Eggs(object):
    def __getattr__(self, attr):
        return decorate(getattr(self, `_` + attr))

有一些丑陋的递归隐藏在那里,你会想要防止,但这是一个开始。

于 2010-08-12T12:44:48.967 回答