I frequently find myself adding "just in time" (JIT) object creation to avoid wasting cycles on objects that are never used. This class enables JIT object creation by basically currying the __init__ method.
The object is instianted only when an attribute is got or set. Then automatic delegation is used to front for the object (see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52295)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | class JIT:
'''
JIT is a class for Just In Time instantiation of objects. Init is called
only when the first attribute is either get or set.
'''
def __init__(self, klass, *args, **kw):
'''
klass -- Class of objet to be instantiated
*args -- arguments to be used when instantiating object
**kw -- keywords to be used when instantiating object
'''
self.__dict__['klass'] = klass
self.__dict__['args'] = args
self.__dict__['kw'] = kw
self.__dict__['obj'] = None
def initObj(self):
'''
Instantiate object if not already done
'''
if self.obj is None:
self.__dict__['obj'] = self.klass(*self.args, **self.kw)
def __getattr__(self, name):
self.initObj()
return getattr(self.obj, name)
def __setattr__(self, name, value):
self.initObj()
setattr(self.obj, name, value)
class TestIt:
def __init__(self, arg, keyword=None):
print 'In TestIt.__init__() -- arg: %s, keyword=%s' % (arg, keyword)
def method(self):
print 'In TestIt.method().'
def oldWay():
# Create t whether or not it gets used.
t = TestIt('The Argument', keyword='The Keyword')
def main():
# JIT refactored
t = JIT(TestIt, 'The Argument', keyword='The Keyword')
print 'Not intstaintiated yet.'
# TestIt object instantiated here.
t.method()
if __name__ == '__main__':
main()
# OUTPUT:
# Not intstaintiated yet.
# In TestIt.__init__() -- arg: The Argument, keyword=The Keyword
# In TestIt.method().
|
Remember the idiom "First make it work, then if it is not fast enough make it work faster." One way to make it work faster is to avoid creating objects that never get used. This can be difficult to do however without a major re-design in some cases.
This recepe is intended to bridge that gap. Refactoring with the JIT class becomes cake-work; just replace object creation with the JIT proxy.
The proxy code does have an overhead and will actually slow the code down if the object is actually created. It should only be used for rarely needed objects.
It would be really cool if the proxy could replace its self with the actual object when the object was created. Any ideas?
You're still creating at least one object, maybe two. Notice that this approach creates two objects whenever you actually need one, and one object when you don't. Unless the initialization of the "real" object is quite costly, you might as well create the one object to begin with.
A better approach for lazy object creation is to use descriptors that create an attribute's value on demand. This lets you have objects that don't do all their initialization in __init__: instead, the object's attributes are created as and when they are needed. Since this can include the creation of other objects, they are not created unless needed. There's no need for proxies in this circumstance, which is good, because proxies like your JIT object are very hard to get right in Python. (Note that your proxy doesn't correctly support any special methods such as __getitem__, __call__, __str__, etc.)
PEAK (http://peak.telecommunity.com/) includes a package called peak.binding that supports creation of such descriptors. Specifically, the 'binding.Make()' descriptor, which accepts a callable (such as a class). So, to redo your example using PEAK:
(Note that if "TestIt" had an expensive initialization itself, the initialization could be split into separate 'binding.Make' calls to create its attributes. Also, you do not have to use 'binding.Component' as a base class, you can also use 'binding.Activator' as a metaclass, or explicitly tell 'Make' what attribute name it will use.)