Recipe6.16.Avoiding the


Recipe 6.16. Avoiding the "Singleton" Design Pattern with the Borg Idiom

Credit: Alex Martelli, Alex A. Naanou

Problem

You want to make sure that only one instance of a class is ever created: you don't care about the id of the resulting instances, just about their state and behavior, and you need to ensure subclassability.

Solution

Application needs (forces) related to the "Singleton" Design Pattern can be met by allowing multiple instances to be created while ensuring that all instances share state and behavior. This is more flexible than fiddling with instance creation. Have your class inherit from the following Borg class:

class Borg(object):     _shared_state = {  }     def _ _new_ _(cls, *a, **k):         obj = object._ _new_ _(cls, *a, **k)         obj._ _dict_ _ = cls._shared_state         return obj

If you override _ _new_ _ in your class (very few classes need to do that), just remember to use Borg._ _new_ _, rather than object._ _new_ _, within your override. If you want instances of your class to share state among themselves, but not with instances of other subclasses of Borg, make sure that your class has, at class scope, the "state"ment:

    _shared_state = {  }

With this "data override", your class doesn't inherit the _shared_state attribute from Borg but rather gets its own. It is to enable this "data override" that Borg's _ _new_ _ uses cls._shared_state instead of Borg._shared_state.

Discussion

Borg in action

Here's a typical example of Borg use:

if _ _name_ _ == '_ _main_ _':     class Example(Borg):         name = None         def _ _init_ _(self, name=None):             if name is not None: self.name = name         def _ _str_ _(self): return 'name->%s' % self.name     a = Example('Lara')     b = Example( )                  # instantiating b shares self.name with a     print a, b     c = Example('John Malkovich')  # making c changes self.name of a & b too     print a, b, c     b.name = 'Seven'               # setting b.name changes name of a & c too      print a, b, c

When running this module as a main script, the output is:

name->Lara name->Lara name->John Malkovich name->John Malkovich name->John Malkovich name->Seven name->Seven name->Seven

All instances of Example share state, so any setting of the name attribute of any instance, either in _ _init_ _ or directly, affects all instances equally. However, note that the instance's ids differ; therefore, since we have not defined special methods _ _eq_ _ and _ _hash_ _, each instance can work as a distinct key in a dictionary. Thus, if we continue our sample code as follows:

    adict = {  }     j = 0     for i in a, b, c:         adict[i] = j         j = j + 1     for i in a, b, c:         print i, adict[i]

the output is:

name->Seven 0 name->Seven 1 name->Seven 2

If this behavior is not what you want, add _ _eq_ _ and _ _hash_ _ methods to the Example class or the Borg superclass. Having these methods might better simulate the existence of a single instance, depending on your exact needs. For example, here's a version of Borg with these special methods added:

class Borg(object):     _shared_state = {  }     def _ _new_ _(cls, *a, **k):         obj = object._ _new_ _(cls, *a, **k)         obj._ _dict_ _ = cls._shared_state         return obj     def _ _hash_ _(self): return 9      # any arbitrary constant integer     def _ _eq_ _(self, other):         try: return self._ _dict_ _ is other._ _dict_ _         except AttributeError: return False

With this enriched version of Borg, the example's output changes to:

name->Seven 2 name->Seven 2 name->Seven 2

Borg, Singleton, or neither?

The Singleton Design Pattern has a catchy name, but unfortunately it also has the wrong focus for most purposes: it focuses on object identity, rather than on object state and behavior. The Borg design nonpattern makes all instances share state instead, and Python makes implementing this idea a snap.

In most cases in which you might think of using Singleton or Borg, you don't really need either of them. Just write a Python module, with functions and module-global variables, instead of defining a class, with methods and per-instance attributes. You need to use a class only if you must be able to inherit from it, or if you need to take advantage of the class' ability to define special methods. (See Recipe 6.2 for a way to combine some of the advantages of classes and modules.) Even when you do need a class, it's usually unnecessary to include in the class itself any code to enforce the idea that one can't make multiple instances of it; other, simpler idioms are generally preferable. For example:

class froober(object):     def _ _init_ _(self):         etc, etc froober = froober( )

Now froober is by nature the only instance of its own class, since name 'froober' has been rebound to mean the instance, not the class. Of course, one might call froober._ _class_ _( ), but it's not sensible to spend much energy taking precautions against deliberate abuse of your design intentions. Any obstacles you put in the way of such abuse, somebody else can bypass. Taking precautions against accidental misuse is way plenty. If the very simple idiom shown in this latest snippet is sufficient for your needs, use it, and forget about Singleton and Borg. Remember: do the simplest thing that could possibly work. On rare occasions, though, an idiom as simple as this one cannot work, and then you do need more.

The Singleton Design Pattern (described previously in Recipe 6.15) is all about ensuring that just one instance of a certain class is ever created. In my experience, Singleton is generally not the best solution to the problems it tries to solve, producing different kinds of issues in various object models. We typically want to let as many instances be created as necessary, but all with shared state. Who cares about identity? It's state (and behavior) we care about. The alternate pattern based on sharing state, in order to solve roughly the same problems as Singleton does, has also been called Monostate. Incidentally, I like to call Singleton "Highlander" because there can be only one.

In Python, you can implement the Monostate Design Pattern in many ways, but the Borg design nonpattern is often best. Simplicity is Borg's greatest strength. Since the _ _dict_ _ of any instance can be rebound, Borg in its _ _new_ _ rebinds the _ _dict_ _ of each of its instances to a class-attribute dictionary. Now, any reference or binding of an instance attribute will affect all instances equally. I thank David Ascher for suggesting the appropriate name Borg for this nonpattern. Borg is a nonpattern because it had no known uses at the time of its first publication (although several uses are now known): two or more known uses are part of the prerequisites for being a design pattern. See the detailed discussion at http://www.aleax.it/5ep.html.

An excellent article by Robert Martin about Singleton and Monostate can be found at http://www.objectmentor.com/resources/articles/SingletonAndMonostate.pdf. Note that most of the disadvantages that Martin attributes to Monostate are really due to the limitations of the languages that Martin is considering, such as C++ and Java, and just disappear when using Borg in Python. For example, Martin indicates, as Monostate's first and main disadvantage, that "A non-Monostate class cannot be converted into a Monostate class through derivation"but that is obviously not the case for Borg, which, through multiple inheritance, makes such conversions trivial.

Borg odds and ends

The _ _getattr_ _ and _ _setattr_ _ special methods are not involved in Borg's operations. Therefore, you can define them independently in your subclass, for whatever other purposes you may require, or you may leave these special methods undefined. Either way is not a problem because Python does not call _ _setattr_ _ in the specific case of the rebinding of the instance's _ _dict_ _ attribute.

Borg does not work well for classes that choose to keep some or all of their per-instance state somewhere other than in the instance's _ _dict_ _. So, in subclasses of Borg, avoid defining _ _slots_ _that's a memory-footprint optimization that would make no sense, anyway, since it's meant for classes that have a large number of instances, and Borg subclasses will effectively have just one instance! Moreover, instead of inheriting from built-in types such as list or dict, your Borg subclasses should use wrapping and automatic delegation, as shown previously Recipe 6.5. (I named this latter twist "DeleBorg," in my paper available at http://www.aleax.it/5ep.html.)

Saying that Borg "is a Singleton" would be as silly as saying that a portico is an umbrella. Both serve similar purposes (letting you walk in the rain without getting wet)solve similar forces, in design pattern parlancebut since they do so in utterly different ways, they're not instances of the same pattern. If anything, as already mentioned, Borg has similarities to the Monostate alternative design pattern to Singleton. However, Monostate is a design pattern, while Borg is not; also, a Python Monostate could perfectly well exist without being a Borg. We can say that Borg is an idiom that makes it easy and effective to implement Monostate in Python.

For reasons mysterious to me, people often conflate issues germane to Borg and Highlander with other, independent issues, such as access control and, particularly, access from multiple threads. If you need to control access to an object, that need is exactly the same whether there is one instance of that object's class or twenty of them, and whether or not those instances share state. A fruitful approach to problem-solving is known as divide and conquermaking problems easier to solve by splitting apart their different aspects. Making problems more difficult to solve by joining together several aspects must be an example of an approach known as unite and suffer!

See Also

Recipe 6.5; Recipe 6.15; Alex Martelli, "Five Easy Pieces: Simple Python Non-Patterns" (http://www.aleax.it/5ep.html).



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net