Подтвердить что ты не робот

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

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

Я предполагаю, что тогда он становится своего рода аспектом, а не декоратором, и он чувствует себя немного странно, но думал о чем-то вроде времени или auth, это было бы довольно аккуратно.

4b9b3361

Ответ 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))

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