Присоединение декоратора ко всем функциям в классе - PullRequest
44 голосов
/ 12 августа 2010

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

IПредположим, что тогда это становится неким декоратором, а не аспектом, и это выглядит немного странно, но он думал о времени или аутентичности, и это было бы довольно аккуратно.

Ответы [ 5 ]

28 голосов
/ 12 августа 2010

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

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

Примечание: он не будет украшать статический метод и метод класса

27 голосов
/ 12 августа 2010

Самый простой способ сделать это или сделать другие изменения в определении класса - это определить метакласс.

В качестве альтернативы просто примените декоратор в конце определения класса:

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 на types.FunctionType.

На практике, конечно, вы захотите применять свой декоратор более избирательно, и как только вы захотите украсить все, кроме одного метода.вы обнаружите, что проще и гибче просто использовать синтаксис декоратора традиционным способом.

1 голос
/ 16 апреля 2017

Конечно, метаклассы - это самый питонический путь, когда вы хотите изменить способ, которым питон создает объекты. Что можно сделать, переопределив метод __new__ вашего класса. Но есть некоторые моменты вокруг этой проблемы (особенно для python 3.X), о которых я хотел бы упомянуть:

  1. types.FunctionType не защищает специальные методы от декорирования, так как они являются типами функций. В качестве более общего способа вы можете просто украсить объекты, имена которых не начинаются с двойного подчеркивания (__). Еще одно преимущество этого метода состоит в том, что он также охватывает те объекты, которые существуют в пространстве имен и начинаются с __, но не функционируют как __qualname__, __module__ и т. Д.
  2. Аргумент namespace в заголовке __new__ не содержит атрибутов класса в __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.

Для проверки 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()}
0 голосов
/ 05 июля 2019

Обновление для 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

(и спасибо Дункану за это)

0 голосов
/ 12 августа 2010

Вы можете переопределить метод __getattr__. На самом деле он не подключает декоратор, но позволяет возвращать декорированный метод. Возможно, вы захотите сделать что-то вроде этого:

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

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

...