Monday, 7 February 2011

Singleton objects

Singleton objects are not some fancy concept but a very practical solution to representing a specific value as an object as Pythons None object shows.

Many programming languages provide syntactical solutions to provide constants. Literals like 123 or "a string" are almost always constants but if you want to give a meaningful name to a constant you need a different approach.

Referring to value by name is simple enough, after all a variable assignment does just that, but you need some way to indicate that that variable shouldn't be altered after the assignment. Python does not provide a way to specify a constant but there are ways around this, see for example (this recipe).

Paradoxically, it is comparatively simple to define a class that allows only a single instantiated object. There are arguments for and against this Singleton Design Pattern and this article is a good starting point if you want to read about it. And whatever the merits of this design pattern, you can't avoid it because many constants in Python are implemented as singleton classes, for example True, False, NotImplemented and None.

And the None implementation illustrates another practical advantage: when comparing a value against a singleton we can check whether they are identical (with the is operator) instead of comparing their values (with the == operator) and although this might not be the primary purpose, it does give us a clear speed advantage as the next snippets shows:

import timeit

s1 = "123 == None"
s2 = "a == None"
s3 = "123 is None"
s4 = "a is None"

count=1000000
t = timeit.Timer(stmt=s1,setup='a=123')
print (s1, "%.2f usec/pass" % (count * t.timeit(number=count)/count))
t = timeit.Timer(stmt=s2,setup='a=123')
print (s2, "%.2f usec/pass" % (count * t.timeit(number=count)/count))
t = timeit.Timer(stmt=s3,setup='a=123')
print (s3, "%.2f usec/pass" % (count * t.timeit(number=count)/count))
t = timeit.Timer(stmt=s4,setup='a=123')
print (s4, "%.2f usec/pass" % (count * t.timeit(number=count)/count))
The results of running this bit of code on my Samsung NC10 netbook give me the following output:
123 == None 0.29 usec/pass
a == None 0.31 usec/pass
123 is None 0.20 usec/pass
a is None 0.21 usec/pass
This is a significant difference and although it doesn't seem to amount to much, this might shave several seconds of your algorithm when you compare for example the results of a large database query, as databases tend to represent NULL values as None.

No comments:

Post a Comment