Skip to content Skip to sidebar Skip to footer

Decorators Versus Inheritance

How do you decide between using decorators and inheritance when both are possible? E.g., this problem has two solutions. I'm particularly interested in Python.

Solution 1:

Decorators...:

  • ...should be used if what you are trying to do is "wrapping". Wrapping consists of taking something, modifying (or registering it with something), and/or returning a proxy object that behaves "almost exactly" like the original.
  • ...are okay for applying mixin-like behavior, as long as you aren't creating a large stack of proxy objects.
  • ...have an implied "stack" abstraction:

e.g.

@decoA@decoB@decoC
def myFunc(...): ...
    ...

Is equivalent to:

def myFunc(...): ...
    ...
myFunc =decoA(decoB(decoC(myFunc)))  #note the *ordering*

Multiple inheritance...:

  • ... is best for adding methods to classes; you cannot use it to decorate functions easily. In this context, it can be used to achieve mixin-like behavior if all you need is a set of "duck-typing style" extra methods.
  • ... may be a bit unwieldy if your problem is not a good match for it, with issues with superclass constructors, etc. For example, the subclasses __init__ method will not be called unless it is called explicitly (via the method-resolution-order protocol)!

To sum up, I would use decorators for mixin-like behavior if they didn't return proxy objects. Some examples would include any decorator which returns the original function, slightly modified (or after registering it somewhere or adding it to some collection).

Things you will often find decorators for (like memoization) are also good candidates, but should be used in moderation if they return proxy objects; the order they are applied matter. And too many decorators on top of one another is using them in a way they aren't intended to be used.

I would consider using inheritance if it was a "classic inheritance problem", or if all I needed for the mixin behavior were methods. A classic inheritance problem is one where you can use the child wherever you could use the parent.

In general, I try to write code where it is not necessary to enhance arbitrary things.

Solution 2:

The problem you reference is not deciding between decorators and classes. It is using decorators, but you have the option of using either:

  • a decorator, which returns a class
  • a decorator, which returns a function

A decorator is just a fancy name for the "wrapper" pattern, i.e. replacing something with something else. The implementation is up to you (class or function).

When deciding between them, it's completely a matter of personal preference. You can do everything you can do in one with the other.

  • if decorating a function, you may prefer decorators which return proxy functions
  • if decorating a class, you may prefer decorators which return proxy classes

(Why is it a good idea? There may be assumptions that a decorated function is still a function, and a decorated class is still a class.)

Even better in both cases would be to use a decorator which just returns the original, modified somehow.

edit: After better understanding your question, I have posted another solution at Python functools.wraps equivalent for classes

Solution 3:

The other answers are quite great, but I wanted to give a succinct list of pros and cons.

The main advantage of mixins is that the type can be checked at runtime using isinstance and it can be checked with linters like MyPy. Like all inheritance, it should be used when you have an is-a relationship. For example dataclass should probably have been a mixin in order to expose dataclass-specific introspection variables like the list of dataclass fields.

Decorators should be preferred when you don't have an is-a relationship. For example, a decorator that propagates documentation from another class, or registers a class in some collection.

Decoration typically only affects the class it decorates, but not classes that inherit from the base class:

@decoratorclassA:
    ...  # Can be affected by the decorator.classB(A):
    ...  # Not affected by the decorator in most cases.

Now that Python has __init_subclass__, everything that decorators can do can be done with mixins, and they typically do affect child subclasses:

class A(Mixin):
    ... # Is affected by Mixin.__init_subclass__.

class B(A):
    ... # Is affected by Mixin.__init_subclass__.

In summary, the questions you should ask when deciding between a mixin and decoration are:

  • Is there an is-a pattern?
    • Would you ever call isinstance?
    • Would you use the mixin in a type annotation?
  • Do you want the behavior to affect child classes?

Solution 4:

If both are equivalent, I would prefer decorators, since you can use the same decorator for many classes, while inheriting apply to only one specific class.

Solution 5:

Personally, I would think in terms of code reuse. Decorator is sometimes more flexible than inheritance.

Let's take caching as an example. If you want to add caching facility to two classes in your system: A and B, with inheritance, you'll probably wind up having ACached and BCached. And by overriding some of the methods in these classes, you'll probably duplicate a lot of codes for the same caching logic. But if you use decorator in this case, you only need to define one decorator to decorate both classes.

So, when deciding which one to use, you may first want to check if the extended functionality is only specific to this class or if the same extended functionality can be reused in other parts of your system. If it cannot be reused, then inheritance should probably do the job. Otherwise, you can think about using decorator.

Post a Comment for "Decorators Versus Inheritance"