Attaching a decorator to all functions within a class

Question:

I don’t really need to do this, but was just wondering, is there a way to bind a decorator to all functions within a class generically, rather than explicitly stating it for every function.

I suppose it then becomes a kind of aspect, rather than a decorator and it does feel a bit odd, but was thinking for something like timing or auth it’d be pretty neat.

Asked By: dochead

||

Answers:

You could override the __getattr__ method. It’s not actually attaching a decorator, but it lets you return a decorated method. You’d probably want to do something like this:

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

There’s some ugly recursion hiding in there that you’ll want to protect against, but that’s a start.

Answered By: nmichaels

The cleanest way to do this, or to do other modifications to a class definition, is to define a metaclass.

Alternatively, just apply your decorator at the end of the class definition using inspect:

import inspect

class Something:
    def foo(self): 
        pass

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

In practice of course you’ll want to apply your decorator more selectively. As soon as you want to decorate all but one method you’ll discover that it is easier and more flexible just to use the decorator syntax in the traditional way.

Answered By: Duncan

Everytime you think of changing class definition, you can either use the class decorator or metaclass. e.g. using metaclass

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

Output:

before func1
after func1

Note: it will not decorate staticmethod and classmethod

Answered By: Anurag Uniyal

Of course that the metaclasses are the most pythonic way to go when you want to modify the way that python creates the objects. Which can be done by overriding the __new__ method of your class. But there are some points around this problem (specially for python 3.X) that I’d like to mention:

  1. types.FunctionType doesn’t protect the special methods from being decorated, as they are function types. As a more general way you can just decorate the objects which their names are not started with double underscore (__). One other benefit of this method is that it also covers those objects that exist in namespace and starts with __ but are not function like __qualname__, __module__ , etc.
  2. The namespace argument in __new__‘s header doesn’t contain class attributes within the __init__. The reason is that the __new__ executes before the __init__ (initializing).

  3. It’s not necessary to use a classmethod as the decorator, as in most of the times you import your decorator from another module.

  4. If your class is contain a global item (out side of the __init__) for refusing of being decorated alongside checking if the name is not started with __ you can check the type with types.FunctionType to be sure that you’re not decorating a non-function object.

Here is a sample metacalss that you can use:

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)

Demo:

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)

Output:

the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.

For checking the 3rd item from the aforementioned notes you can uncomment the line a = 10 and do print(myinstance.a) and see the result then change the dictionary comprehension in __new__ as follows then see the result again:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)
             else my_decorator(v) for k, v in namespace.items()}
Answered By: Mazdak

Update for 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

(and thanks to Duncan for this)

Answered By: Doha Simon

Following code works for python2.x and 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)
Answered By: pr-pal

I will repeat my answer here, for a similar issue

It can be done many different ways. I will show how to make it through meta-class, class decorator and inheritance.

by changing meta class

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

Also, will show two approaches how to make it without changing meta information of class (through class decorator and class inheritance). The first approach through class decorator put_decorator_on_all_methods accepts decorator to wrap all member callable objects of class.

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

And, recently, I’ve come across on the same problem, but I couldn’t put decorator on class or change it in any other way, except I was allowed to add such behavior through inheritance only (I am not sure that this is the best choice if you can change codebase as you wish though).

Here class Logger forces all callable members of subclasses to write information about their invocations, see code below.

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

Or more abstractly, you can instantiate base class based on some decorator.

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
Answered By: Alex

There’s another slightly similar thing you might want to do in some cases. Sometimes you want to trigger the attachment for something like debugging and not on all the classes but for every method of an object you might want a record of what it’s doing.

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

This function will go through some objects (only self currently) and replace all functions and methods that do not start with _ with a debugging decorator.

The method used for this of just iterating the dir(self) is not addressed above but totally works. And can be called externally from the object and much more arbitrarily.

Answered By: Tatarize

In Python 3 you could also write a simple function that overwrites/applies a decorator to certain methods like so:

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
Answered By: Amo

I came to this question from:
How to decorate all functions of a class without typing it over and over for each method?
And I want add a one note:

Answers with class decorators or repalcing class methods like this one:
https://stackoverflow.com/a/6307868/11277611
Will not work with staticmethod.
You will get TypeError, unexpected argument because your method will get self/cls as first argument.
Probably:
Decorated class doesn’t know about decorators of self methods and can’t be distincted even with inspect.ismethod.

I come to such quickfix:
I’m not checked it closely but it passes my (no so comprehensive) tests.
Using dynamically decorators is already a bad approach, so, it must be okay as temporary solution.

TLD:TD Add try/exception to use with staticmethod

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
Answered By: Давид Шико

Combining information from various answers, here’s a DecorateMethods metaclass:

class DecorateMethods(type):
    """ Decorate all methods of the class with the decorator provided """

    def __new__(cls, name, bases, attrs, **kwargs):
        try:
            decorator = kwargs['decorator']
        except KeyError:
            raise ValueError('Please provide the "decorator" argument, eg. '
                             'MyClass(..., metaclass=DecorateMethods, decorator=my_decorator)')

        exclude = kwargs.get('exclude', [])

        for attr_name, attr_value in attrs.items():

            if isinstance(attr_value, types.FunctionType) and 
                    attr_name not in exclude and 
                    not attr_name.startswith('__'):
                attrs[attr_name] = decorator(attr_value)

        return super(DecorateMethods, cls).__new__(cls, name, bases, attrs)

Used as:

class MyClass(metaclass=DecorateMethods, decorator=my_decorator, exclude=["METHOD_TO_BE_EXCLUDED"]):
    ...

It works nicely with unittests, as opposed to function-based solutions.


Credit to answers in 1, 2 and other answers in this question.

Answered By: Voy