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?
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.
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.