I'm doing a lot of web2py programming lately, and much of my time is spent 
building helper classes that act as "factories" for high-powered web 
gadgets.  i have a class that builds jQuery DataTables, for example, and 
another that builds "widgets" as defined in my templating system (each of 
which is an elaborate construct of header, toolbar, body, etc.).

It occurred to me that many of these objects were by nature a "singleton" 
-- that is, a single instance of the object (the factory in this case) 
could be used over and over.  The alternative is to create a new instance 
and initialize it each time you use it -- and since it is used several 
times on each page, that means a lot of instances created and destroyed.

So how do you make something a singleton?  Step Zero is to make sure we are 
building these classes in a "module" instead of a model or a controller. 
 These must be compiled for their magic goodness to work properly. 
 Otherwise, there's no savings of effort, but the opposite!

Step one is to ensure there is nothing in the object's instance vars which 
is unique to that instance.  In other words, anything that changes from one 
use of the object to the next must be passed in from outside.  In my case 
the widgets are all driven from the database, so they don't hold any 
internal state to worry about.

Next, we need to determine how unique these instances are -- is there only 
one?  Is there potentially a different one on each distinct web page 
(controller/function)?  We use this to create a dictionary of instances 
that stores the object we create for each page in between invocations.

Last, we implement a "metaclass" that take care of the lookup of the 
singleton instance, or creates a new one if the singleton dictionary is 
empty.  The metaclass is the cleanest way of creating a Singleton.  There 
are other patterns in Python, but they all suffer from one problem or 
another.  Some replace the class object with a function -- those won't work 
if you subclass anything.  Of the three or four patterns I tried, this is 
the best.

Singleton class definition:

#!/usr/bin/env python
# coding: utf8

from gluon.globals import current

_instances = {}
class Singleton(type):
    def __call__(cls, *args, **kwargs):
        cr = current.request
        key = ('/').join([cr.controller,cr.function])
        if key not in _instances:
            _instances[key] = super(Singleton, cls).__call__(*args, 
**kwargs)
        return _instances[key]


This class keeps a dictionary of instances and uses a key of 
"contoller/function" from the current request to look up the Singleton. 
 Other dictionary keys are useful in other scenarios.  Also note that 
Singleton is a subclass of "type", not "object" as you may expect. 

To make a class (or even a superclass of classes) a Singleton, just declare 
the metaclass like so:


class WidgetHandler(object):
    if current.settings.singleton:
        __metaclass__ = Singleton
    
    def __init__(self, controller=None, title='', id=None, theme=None ):
        from gluon.storage import Storage
        from fcn import make_theme
        self.controller = controller
        self.params = Storage()
        self.id = id
        self.title = title
        self.theme = make_theme(theme)
        self.api = ['widget','content']
        self.scripts = []
        self.html_cache = None
        self.script_cache = None
        self.init_datatables()


The critical business is highlighted.  just declare this class to use 
"Singleton" as metaclass and it will magically become a Singleton.  I left 
the __init__ showing so you can see how elaborate initialization can be. 
 The call to "init_datatables" creates and initializes a jQuery Datatables 
object that, itself, requires a lot of steps.  It then builds the HTML 
representation of the widget, the javascripts that go with it, and so 
forth.  All of that effort is saved by bypassing this initialization each 
time.

In my case, WidgetHandler is itself used as a superclass by numerous 
WidgetXXX classes.  It holds the common template and reduces the amount of 
code in each widget class.  It also provides a single point of control for 
all of the common code.  Singleton "decorator" patterns do not work when 
creating a superclass because the class name is replaced with a function 
that generates a class, so super() fails to work properly.

Note the use of the global option "current.settings.singleton" to turn the 
Singleton feature off for debugging.  This will save some hair pulling as 
you work on the code, expecting it to use the code you just edited when the 
object has been cached instead!  It also provides an easy way to compare 
speed and memory usage with and without the Singleton feature.


-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to