Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply a python decorator to all inheriting classes

Tags:

python

django

In my django project (django 1.6 - soon upgrading to 1.9, python 2.7),

I'd like to apply a decorator on all my project's model classes (20-30 classes). All of these classes already inherit from a parent class called 'LinkableModel', which I wrote for a certain (non-related) purpose.

Now, I'd like to apply a class decorator to all these models. (specifically I'm referring to decorator 'python_2_unicode_compatible': https://docs.djangoproject.com/en/1.9/ref/utils/#django.utils.encoding.python_2_unicode_compatible).

When I add this decorator to their parent class 'LinkableModel', it's not inherited. Is there any way to apply a decorator to multiple classes, without adding it to each and every class?

(Theoretically I even don't mind if this decorator will be by default applied to all classes in my project...)

Code snippet:

@python_2_unicode_compatible
class LinkableModel(models.Model):
    ...
    ...
    ...

class MyModel1(LinkableModel):
    ...
    ...
    ...

class MyModel2(LinkableModel):
    ...
    ...
    ...
like image 687
o_c Avatar asked Feb 25 '16 10:02

o_c


3 Answers

In Python 3.7 now you can do it this way:

  class ParentClass:
      def __init_subclass__(cls, **kwargs):
          return your_decorator(_cls=cls)

it will apply decorator for each subclass of ParentClass

UPDATED: full example:

def your_decorator(_cls):
    print("Hello, I'm decor!")
    def wrapper():
        return _cls()
    return wrapper


class ParentClass:
    def __init_subclass__(cls, **kwargs):
        return your_decorator(_cls=cls)


class A(ParentClass):
    pass

a = A()
like image 191
Yuliya Volkova Avatar answered Nov 14 '22 22:11

Yuliya Volkova


There is (as far as I know) no simple way, because you cannot inherit decorators.

The simplest solution I can imagine is:

globals_ = globals()
for name, cls in globals_.items():
    if subclass(cls, Base):
        globals_[name] = decorator(cls)

It simply iterates over every global variable already defined in current module, and if it happens to be class inheriting from Base (or Base itself), it decorates it with decorator.

Note that subclass will not be decorated if:

  • it's created after this snippet,
  • it's created in another module,
  • it's not defined in global namespace.

Alternatively, you can use metaclass:

class Decorate(type):
    def __new__(mcls, name, bases, attrs):
        return decorator(super().__new__(name, bases, attrs))

class Base(metaclass=Decorate):
    pass

When you write class Base(metaclass=Decorate):, Python uses Decorate to create Base and its subclasses.

All that Decorate does, is to decorate class using decorator before returning it.

If you use this, you will probably have a problem if you try to inherit from 2 (or more) classes, each with different metaclass.

like image 41
GingerPlusPlus Avatar answered Nov 14 '22 21:11

GingerPlusPlus


I used the answer by @GingerPlusPlus, and created the following function, to apply a decorator to all subclasses of a class:

 def apply_decorator_to_all_subclasses(globals_, base_class, decorator):
    """
    Given a 'globals_' dictionary, a base class, and a decorator - this function applies the decorator to all the defined classes that derive from the base class
    Note!: this function should be called only *after* the subclassess were declared
    :param globals_: the given output of globals(), in the caller's context
    :param base_class: the class whose descendants require the decorator
    :param decorator: the decorator to apply
    """
    for name, cls in globals_.items():
        # Applying only on *class* items, that are descandants of base_class
        if inspect.isclass(cls) and issubclass(cls, base_class) and cls != base_class:
            globals_[name] = decorator(cls)
like image 26
o_c Avatar answered Nov 14 '22 23:11

o_c