Search code examples
pythondecoratorlazy-initialization

Use a class decorator to implement late-initialization


I am using some classes which need to connect to databases. The connection is only really needed when performing a real action. I want to delay the connection phase until it is really needed. For that, I want to do something similar to this:

class MyClass

    def __init__(self):
        self.conn = None

    def connect(self):
        if self.conn : return
        self.conn = ConnectToDatabase()

    @connect
    def do_something1(self):
        self.conn.do_something1()

    @connect
    def do_something2(self):
        self.conn.do_something2()

But I do not know how to define the connect decorator for the class.

I could of course do something like this:

    def do_something1(self):
        self.connect()
        self.conn.do_something1()

But using decorators seems a more readable solution. Is it possible?


Solution

  • Rather than trying to decorate the functions that require connections, use a property for getting the connection itself.

    class MyClass(object):
    
        def __init__(self):
            self._conn = None
    
        @property
        def conn(self):
            if self._conn is None:
                self._conn = ConnectToDatabase()
            return self._conn
    
        def do_something1(self):
            self.conn.do_something1()
    
        def do_something2(self):
            self.conn.do_something2()
    

    As for a straight decorator example, playing off F.J's answer:

    def prerequisite(prerequisite_function, *pre_args, **pre_kwargs):
        def wrapper(func):
            def wrapped(self, *args, **kwargs):
                prerequisite_function(self, *pre_args, **pre_kwargs)
                return func(self, *args, **kwargs)
            return wrapped
        return wrapper
    
     class MyClass(object):
    
         def __init__(self):
             self.conn = None
    
         def connect(self):
             if self.conn is None:
                 self.conn = ConnectToDatabase()
    
         @prerequisite(connect)
         def do_something(self):
             self.conn.do_something()
    

    You could also make prerequisite more robust by making it create descriptors so that it can behave correctly for functions and static methods as well as class and instance methods.