Question

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?

Was it helpful?

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.

OTHER TIPS

I do like sr2222's approach of using a property for getting the connection, however here is an approach with decorators which may be useful or at least informative (use of functools.wraps() is optional):

import functools

def require_connection(f):
    @functools.wraps(f)
    def wrapped(self, *args, **kwargs):
        self.connect()
        return f(self, *args, **kwargs)
    return wrapped

class MyClass(object):
    def __init__(self):
        self.conn = None

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

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

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

Similar to sr2222's solution, but calling it what it is: a cached_property.

The code is more compact, uses reusable building blocks, and in my opinion more readable.

class MyClass(object):

    @cached_property
    def conn(self):
        return ConnectToDatabase()

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

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

The definition of cached_property is found here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top