Welcome, guest | Sign In | My Account | Store | Cart

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)

Python, 57 lines
 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?

1 comment

Phillip J. Eby 20 years, 4 months ago  # | flag

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:

from peak.api import binding

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().'

class Holder(binding.Component):
    t = binding.Make(lambda: TestIt('The Argument','The Keyword'))

h = Holder()   # make a holder
h.t.method()   # accessing h.t creates a TestIt instance
h.t.method()   # accessing it again uses the created instance

(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.)

Created by Justin Shaw on Wed, 10 Dec 2003 (PSF)
Python recipes (4591)
Justin Shaw's recipes (11)

Required Modules

  • (none specified)

Other Information and Tasks