将修饰符附加到类中的所有函数

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

我认为它成为了一个方面,而不是一个装饰,它确实感觉有点奇怪,但考虑到一些东西,如时间或授权,这将是相当整洁。

24430 次浏览

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

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

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

完成此操作或对类定义进行其他修改的最干净的方法是定义一个元类。

或者,使用 inspect在类定义的末尾应用装饰器:

import inspect


class Something:
def foo(self):
pass


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

当然,在实践中,你会希望更有选择性地应用你的装潢师。只要您想要修饰所有的方法(除了一种方法之外) ,您就会发现按照传统的方式使用修饰符语法更容易、更灵活。

每次你想改变类定义的时候,你可以使用类修饰符或者元类

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

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

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

  3. 没有必要使用 classmethod作为装饰器,因为在大多数情况下,您可以从另一个模块导入装饰器。

  4. 如果你的类包含一个全局项目(在 __init__之外) ,因为它拒绝被修饰,同时检查名字是否以 __开头,你可以用 types.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)

演示:

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()}

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

(感谢邓肯为此)

下面的代码适用于 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

此外,将显示两种方法,如何使不改变类的元信息(通过 班级装潢师类继承)。通过 班级装潢师put_decorator_on_all_methods的第一种方法接受 decorator 来包装类的所有成员可调用对象。

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

在某些情况下,您可能还想做另一件稍微相似的事情。有时候,您希望触发某些附件,比如调试,而不是针对所有类,而是针对对象的每个方法,您可能希望记录对象正在执行的操作。

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) ,并用调试修饰符替换所有不以 _ 开始的函数和方法。

用于迭代目录(自身)的方法在上面没有提到,但是完全有效。并且可以从对象外部调用,而且更加任意。

在 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

我想到这个问题是因为:
如何装饰一个类的所有函数,而不用为每个方法一遍又一遍地输入它?
我还想补充一点:

使用类修饰符或重置类方法来回答这个问题:
Https://stackoverflow.com/a/6307868/11277611
不能与 staticmethod一起工作。
您将获得 TypeError, unexpected argument,因为您的方法将获得 self/cls作为第一个参数。 可能是:
装饰类不知道 self 方法的装饰符,即使使用 inspect.ismethod也无法区分。

我来得如此匆忙:
我没有仔细检查它,但它通过了我的(没有那么全面)测试。
使用动态装饰符已经是一种糟糕的方法,因此,作为临时解决方案,它必须是可以接受的。

TLD: TD 添加 try/exceptionstaticmethod一起使用

def log_sent_data(function):
@functools_wraps(function)
def decorator(*args, **kwargs):
# Quickfix
self, *args = args
try:  # If method has self/cls/descriptor
result = function(self, *args, **kwargs)
except TypeError:
if args:  # If method is static but has positional args
result = function(*args, **kwargs)
else:  # If method is static and doesn't has positional args
result = function(**kwargs)
# End of quickfix
return result
return decorator