将装饰器附加到类中的所有函数
Posted
技术标签:
【中文标题】将装饰器附加到类中的所有函数【英文标题】:Attaching a decorator to all functions within a class 【发布时间】:2011-03-28 21:46:30 【问题描述】:我真的不需要这样做,但只是想知道,有没有办法将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式声明它。
我想它会变成一种方面,而不是装饰器,它确实感觉有点奇怪,但考虑到时间或身份验证之类的东西会很整洁。
【问题讨论】:
【参考方案1】:执行此操作或对类定义进行其他修改的最简洁方法是定义元类。
或者,只需使用 inspect
在类定义的末尾应用您的装饰器:
import inspect
class Something:
def foo(self):
pass
for name, fn in inspect.getmembers(Something, inspect.isfunction):
setattr(Something, name, decorator(fn))
在实践中,您当然希望更有选择性地应用您的装饰器。当您想要装饰除一种方法之外的所有方法时,您会发现仅以传统方式使用装饰器语法更容易、更灵活。
【讨论】:
如果您不希望未绑定的方法(即当您在类的主体之外定义一个函数,然后执行class Something: foo = function_you_defined
之类的操作)进行修饰,则使用inspect.ismethod
而不是inspect.isfunction
。另请注意,即使使用isfunction
,内置函数也不会被装饰,例如如果你有class Sommething: foo = len
,那么答案中的代码,foo
不会被装饰,你需要使用inspect.isroutine
来处理这种情况。【参考方案2】:
在 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
【讨论】:
这很有希望,但如果你的类中有任何属性,就会出现问题。【参考方案3】:Python 3 更新:
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(DecoMeta, cls).__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
(感谢邓肯)
【讨论】:
在if isinstance
语句之后需要一个缩进块。感谢 3.X 的更新
还应该包括import types
。您还可以将DecoMeta, cls
从super
调用中删除为return super().__new__(cls, name, bases, attrs)
。在 python3 中使用它是class MyKlass(metaclass=MetaGetUserData):
。感谢 3.X 的更新!【参考方案4】:
在某些情况下,您可能还想做另一件稍微类似的事情。有时您想触发附件以进行调试等操作,而不是针对所有类,而是针对对象的每个方法,您可能希望记录其正在执行的操作。
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) 的方法,但完全有效。并且可以从对象外部调用,而且可以任意调用。
【讨论】:
【参考方案5】:我将在这里重复我的答案,类似issue
它可以通过许多不同的方式完成。我将展示如何通过元类、类装饰器和继承来实现。
通过更改元类
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
【讨论】:
您的装饰器解决方案似乎不允许从logger
装饰器访问self
。【参考方案6】:
以下代码适用于 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)
【讨论】:
【参考方案7】:当然,当您想要修改 python 创建对象的方式时,元类是最 Python 的方式。这可以通过覆盖类的__new__
方法来完成。但是我想提一下围绕这个问题(特别是对于 python 3.X)的一些要点:
types.FunctionType
不保护特殊方法不被修饰,因为它们是函数类型。作为一种更通用的方式,您可以只装饰名称不是以双下划线开头的对象 (__
)。此方法的另一个好处是它还涵盖了存在于命名空间中并以__
开头但功能不像__qualname__
、__module__
等的对象。
__new__
标头中的 namespace
参数不包含 __init__
中的类属性。原因是__new__
在__init__
之前执行(初始化)。
没有必要使用 classmethod
作为装饰器,因为大多数时候你从另一个模块导入你的装饰器。
__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.
要检查上述注释中的第 3 项,您可以取消注释行 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()
【讨论】:
【参考方案8】:每当您想到更改类定义时,您可以使用类装饰器或元类。例如使用元类
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
【讨论】:
很棒的信息。我使用这种技术在Gold/approved file testing all methods in a test class against every file in a directory via metaclass metaprogramming in Python 创建了测试方法。 在元类方面,我不太了解__new__
和__init__
之间的区别。两者似乎都可以解决这个问题,尽管它们的论点不同。
@Pat,它类似于__new__
和__init__
对于普通类,调用__new__
来构造对象(在这种情况下是类),调用__init__
来初始化它对象(在这种情况下为类),因此在大多数情况下,除非您需要在类/对象创建之前或之后做某事,否则这可能无关紧要。
感谢@AnuragUniyal。 eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example 对解释也很有帮助。【参考方案9】:
您可以覆盖__getattr__
方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。你可能想做这样的事情:
class Eggs(object):
def __getattr__(self, attr):
return decorate(getattr(self, `_` + attr))
隐藏在其中的一些丑陋的递归是您想要防止的,但这是一个开始。
【讨论】:
你不是说__getattribute__
吗?
@JaceBrowning:不,我不这么认为。如果 Eggs.attr 实际上被称为 Eggs._attr,这将起作用。如果您想覆盖对 Eggs.attr 的访问,这是一个实际属性,那么也许可以。不过,这就是棘手的递归问题的来源。
我明白你现在在做什么——你未修饰的方法都以_
开头。你可以使用__getattribute__
来避免这种情况,但你是对的,有一些棘手的递归需要避免。以上是关于将装饰器附加到类中的所有函数的主要内容,如果未能解决你的问题,请参考以下文章