Decorator Pattern

Warning

Decorator is pattern is different than python decorators such as @classmethod, @staticmethod or any custom python decorator etc.

In Decorator pattern, an class implements the same interface of the instance it wraps, and delegates the call to actual method call with some extra work done.

In theory, if an instance is wrapped by another class using decorator pattern, can be passed to anywhere where the actual instance was meant to be used.

Decorator pattern in static languages

To implement decorator pattern in static language such as c++ and java, we need to implement all methods, getters and setters. This is a tedious task.

Decorator in dynamic languages

To implement decorator pattern in dynamic languages such python specifically, we can only implement methods which are required.

class FileWithLogger:
    def __init__(self, fs, logger):
        self.fs = fs
        self.logger = logger
    def write(self, text):
        self.fs.write(text)
        self.logger.log(text)

However, there might be issues if at some area, some other method is required which is not implemented by decorator wrapper. For example, FileWithLogger cannot be used as context manager using with because it does not implement methods __enter__ and __exit__ required for with.

This problem can be solved by using dynamic wrapper where wrapper implements __getattr__ and __setattr__.

class FileWithLogger:
    def __init__(self, fs, logger):
        self.fs = fs
        self.logger = logger
    def write(self, text):
        self.fs.write(text)
        self.logger.log(text)
        
    def __iter__(self):
        return self.__dict__['fs'].__iter__()
 
    def __next__(self):
        return self.__dict__['fs'].__next__()
 
    def __getattr__(self, name):
        return getattr(self.__dict__['fs'], name)
 
    def __setattr__(self, name, value):
        if name in ('fs', 'logger'):
            self.__dict__[name] = value
        else:
            setattr(self.__dict__['fs'], name, value)
 
    def __delattr__(self, name):
        delattr(self.__dict__['fs'], name)

However, dynamic wrapper like this will not work for introspection where code analyses the code. Code introspecting the wrapper will find out that this not the actual instance, instead a wrapper.

So does the python patterns guide maxim say,

“The Decorator Pattern in Python supports programming — but not metaprogramming.”

References

https://python-patterns.guide/gang-of-four/decorator-pattern/