Прикрепление декоратора ко всем функциям внутри класса
Мне действительно не нужно это делать, но было просто интересно, есть ли способ связать декоратор со всеми функциями внутри класса в целом, а не явно указывать его для каждой функции.
Я предполагаю, что тогда он становится своего рода аспектом, а не декоратором, и он чувствует себя немного странно, но думал о чем-то вроде времени или auth, это было бы довольно аккуратно.
Ответы
Ответ 1
Самый чистый способ сделать это или сделать другие изменения в определении класса - это определить метакласс.
В качестве альтернативы просто примените свой декоратор в конце определения класса:
class Something:
def foo(self): pass
for name, fn in inspect.getmembers(Something):
if isinstance(fn, types.UnboundMethodType):
setattr(Something, name, decorator(fn))
Для Python 3 замените types.UnboundMethodType с типами .FunctionType.
На практике, конечно, вы захотите применить свой декоратор более выборочно, и как только вы захотите украсить все, кроме одного метода, вы обнаружите, что проще и гибче просто использовать синтаксис декоратора в традиционном путь.
Ответ 2
Каждый раз, когда вы думаете об изменении определения класса, вы можете использовать декоратор класса или метакласс. например использование метакласса
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
Ответ 3
Следующий код работает для 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)
Ответ 4
Конечно, метаклассы - это самый пифонический путь, когда вы хотите изменить способ создания объектов python. Это можно сделать, переопределив метод __new__
вашего класса. Но есть некоторые моменты вокруг этой проблемы (специально для python 3.X), которые я хотел бы упомянуть:
-
types.FunctionType
не защищает специальные методы от украшения, поскольку они являются типами функций. Как более общий способ вы можете просто украсить объекты, имена которых не начинаются с двойным подчеркиванием (__
). Другим преимуществом этого метода является то, что он также охватывает те объекты, которые существуют в пространстве имен, и начинается с __
, но не функционирует как __qualname__
, __module__
и т.д.
-
Аргумент namespace
в заголовке __new__
не содержит атрибутов класса в __init__
. Причина в том, что __new__
выполняется перед __init__
(инициализацией).
-
Не нужно использовать classmethod
в качестве декоратора, так как в большинстве случаев вы импортируете свой декоратор из другого модуля.
- Если ваш класс содержит глобальный элемент (вне стороны
__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.
Для проверки 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()}
Ответ 5
Обновление для 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
(и спасибо Дункану за это)
Ответ 6
Вы можете переопределить метод __getattr__
. На самом деле он не прикрепляет декоратор, но позволяет вернуть оформленный метод. Вероятно, вы захотите сделать что-то вроде этого:
class Eggs(object):
def __getattr__(self, attr):
return decorate(getattr(self, `_` + attr))
Там есть какая-то уродливая рекурсия, которую вы хотите защитить, но это начало.