3.3. Function Decorator with Classes

3.3.1. Syntax

  • decorator is a decorator name

  • MyClass is a class name

Syntax:
@decorator
class MyClass:
    pass
Is equivalent to:
MyClass = decorator(MyClass)

3.3.2. Definition

  • decorator is decorator name

  • cls is a pointer to class which is being decorated (MyClass in this case)

  • Decorator must return pointer to Wrapper

  • Wrapper is a closure class

  • Wrapper name is a convention, but you can name it anyhow

  • Wrapper inherits from MyClass so it is almost identical

  • decorator must return pointer to Wrapper

def decorator(cls):
    class Wrapper(cls):
        attribute = 'some value...'
    return Wrapper

3.3.3. Usage

def decorator(cls):
    class Wrapper(cls):
        attribute = 'some value...'
    return Wrapper


@decorator
class MyClass:
    pass


print(MyClass.attribute)
# some value...

3.3.4. Examples

3.3.4.1. Singleton

def singleton(cls):

    def wrapper(*args, **kwargs):
        if not hasattr(cls, '_instance'):
            print('First use, creating instance')
            instance = object.__new__(cls, *args, **kwargs)
            setattr(cls, '_instance', instance)
        else:
            print('Reusing instance')
        return getattr(cls, '_instance')

    return wrapper


@singleton
class DatabaseConnection:
    def connect(self):
        print(f'Connecting... using {self._instance}')


a = DatabaseConnection()    # First use, creating instance
a.connect()                 # Connecting... using <__main__.singleton.<locals>.Wrapper object at 0x10372d310>

b = DatabaseConnection()    # Reusing instance
b.connect()                 # Connecting... using <__main__.singleton.<locals>.Wrapper object at 0x10372d310>

3.3.4.2. Singleton

def singleton(cls):

    class Wrapper(cls):
        def __new__(cls, *args, **kwargs):

            if not hasattr(cls, '_instance'):
                print('First use, creating instance')
                instance = object.__new__(cls, *args, **kwargs)
                setattr(cls, '_instance', instance)
            else:
                print('Reusing instance')
            return getattr(cls, '_instance')

    return Wrapper


@singleton
class DatabaseConnection:
    def connect(self):
        print(f'Connecting... using {self._instance}')


a = DatabaseConnection()    # First use, creating instance
a.connect()                 # Connecting... using <__main__.singleton.<locals>.Wrapper object at 0x10372d310>

b = DatabaseConnection()    # Reusing instance
b.connect()                 # Connecting... using <__main__.singleton.<locals>.Wrapper object at 0x10372d310>