Python 将装饰程序附加到类中的所有函数
我真的不需要这样做,但我只是想知道,有没有一种方法可以将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式地声明它Python 将装饰程序附加到类中的所有函数,python,class,oop,decorator,class-method,Python,Class,Oop,Decorator,Class Method,我真的不需要这样做,但我只是想知道,有没有一种方法可以将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式地声明它 我想它会变成一种方面,而不是一个装饰器,它确实感觉有点奇怪,但考虑到计时或身份验证之类的东西,它会非常整洁。您可以覆盖\uu getattr\uu方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。您可能希望执行以下操作: class Eggs(object): def __getattr__(self, attr): return decorate
我想它会变成一种方面,而不是一个装饰器,它确实感觉有点奇怪,但考虑到计时或身份验证之类的东西,它会非常整洁。您可以覆盖
\uu getattr\uu
方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。您可能希望执行以下操作:
class Eggs(object):
def __getattr__(self, attr):
return decorate(getattr(self, `_` + attr))
有一些丑陋的递归隐藏在其中,您可能想对其进行保护,但这只是一个开始。要做到这一点,或者对类定义进行其他修改,最干净的方法就是定义一个元类 或者,只需在类定义的末尾使用以下命令应用修饰符:
在实践中,您当然会希望更有选择性地应用您的装饰器。只要你想修饰除一个方法之外的所有方法,你就会发现仅仅以传统的方式使用修饰器语法更容易、更灵活。每次你想更改类定义时,你都可以使用类修饰器或元类。e、 g.使用元类
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
the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.
注意:它不会修饰staticmethod和classmethod,当然,当您想要修改python创建对象的方式时,元类是最适合python的方式。这可以通过重写类的
\uuuu new\uuu
方法来完成。但围绕这个问题(特别是Python3.X),我想提到以下几点:
types.FunctionType
不保护特殊方法不被修饰,因为它们是函数类型。作为一种更通用的方式,您可以只装饰名称不以双下划线(\uuuuu
)开头的对象。此方法的另一个好处是,它还涵盖了命名空间中存在的对象,这些对象以\uuuuu
开头,但不是像\uuuuuuuuqalname\uuuuu
和\uuuuuu模块
这样的函数的头中的名称空间
参数在\uuuuuuu new\uuuuu
中不包含类属性。原因是\uuuu new\uuuu
在\uuuu init\uuuuu
(初始化)之前执行
classmethod
作为decorator,因为在大多数情况下,您都是从另一个模块导入decorator的\uu init\uuuuuu
的外侧),用于拒绝在检查名称是否以\uuuuu
开头的同时进行修饰,则可以使用类型.FunctionType
检查该类型,以确保您没有修饰非函数对象以下是您可以使用的示例元代码:
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)
演示:
输出:
before func1
after func1
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)
,查看结果,然后在\uuuuu new\uuuu
中更改字典理解,如下所示,然后再次查看结果:
namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
else my_decorator(v) for k, v in namespace.items()}
Python 3的更新:
class-DecoMeta(类型):
定义(cls、名称、基数、属性):
对于attr\u name,attrs.items()中的attr\u值:
如果isinstance(属性值,类型.函数类型):
attrs[attr\u name]=cls.deco(attr\u值)
返回super(DecoMeta,cls)。\uuuu new\uuuu(cls,name,base,attrs)
@类方法
def装饰(cls,func):
def包装(*args,**kwargs):
打印(“之前”,函数名)
结果=函数(*args,**kwargs)
打印(“之后”,函数名)
返回结果
返回包装器
(感谢Duncan的帮助)以下代码适用于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)
我将在这里重复我的答案,以获得类似的结果 它可以通过许多不同的方式来实现。我将展示如何通过元类、类装饰器和继承实现它 通过更改元类
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
最近,我遇到了同样的问题,但我不能将decorator放在类上或以任何其他方式更改它,除非我被允许只通过继承添加这样的行为(如果您可以随心所欲地更改代码库,我不确定这是否是最佳选择)
这里classLogger
强制子类的所有可调用成员编写有关其调用的信息,请参见下面的代码
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
在某些情况下,您可能还想做另一件稍微类似的事情。有时,您希望为调试之类的事情触发附件,而不是在所有类上,而是在对象的每个方法上,您可能希望记录它正在执行的操作
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),并用调试装饰器替换所有不以u开头的函数和方法
上面没有提到只迭代dir(self)的方法,但它完全有效。在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
你是说
\uuu getattribute\uuuu
?@JaceBrowning:不,我不这么认为。如果Eggs.attr实际上被称为Eggs.\u attr,这将起作用。如果您想覆盖对Eggs.attr的访问,这是一个实际属性,那么可能。这就是棘手的递归方法的用武之地。我知道你现在在做什么——你未修饰的方法都是从\uuu
开始的。您可以使用\uuuu getattribute\uuuu
来避免这种情况,但您是对的,其中有一些棘手的递归需要避免。非常好的信息。我使用这种技术来创建t
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