Прикрепление декоратора ко всем функциям внутри класса

Мне действительно не нужно это делать, но было просто интересно, есть ли способ связать декоратор со всеми функциями внутри класса в целом, а не явно указывать его для каждой функции.

Я предполагаю, что тогда он становится своего рода аспектом, а не декоратором, и он чувствует себя немного странно, но думал о чем-то вроде времени или 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))

Там есть какая-то уродливая рекурсия, которую вы хотите защитить, но это начало.