I l@ve RuBoard |
5.23 Avoiding the Singleton Design Pattern with the Borg IdiomCredit: Alex Martelli 5.23.1 ProblemYou want to make sure that only one instance of a class is ever created, but you don't care about the id of the resulting instances, just about their state and behavior. 5.23.2 SolutionJust about every application need related to Singleton is met by ensuring that all instances share state and behavior, which is easier and more flexible than fiddling with instance creation. Just subclass the following Borg class: class Borg: _shared_state = {} def _ _init_ _(self): self._ _dict_ _ = self._shared_state Ensure that Borg._ _init_ _ is called, just as you always do for every base class's constructor, and you're home free. 5.23.3 DiscussionHere's a typical example of Borg use: if _ _name_ _ == '_ _main_ _': class Example(Borg): def _ _init_ _(self, name=None): Borg._ _init_ _(self) if name is not None: self.name = name def _ _str_ _(self): return 'Example(%s)' % self.name a = Example('Lara') b = Example( ) print a, b c = Example('Boris') print a, b, c b.name = 'Marcel' print a, b, c When running this module as a main script, the output is: Example(Lara) Example(Lara) Example(Boris) Example(Boris) Example(Boris) Example(Marcel) Example(Marcel) Example(Marcel) 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 their id differs, so since we have not defined _ _eq_ _ and _ _hash_ _, they are distinct keys in a dictionary. Thus, if we continued 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 would be: Example(Marcel) 0 Example(Marcel) 1 Example(Marcel) 2 If that's not what you want, you can add _ _eq_ _ and _ _hash_ _ to the Example class or the Borg class. For example, here are these special methods added to Borg: class Borg: _shared_state = {} def _ _init_ _(self): self._ _dict_ _ = self._shared_state def _ _hash_ _(self): return 1 def _ _eq_ _(self, other): try: return self._ _dict_ _ is other._ _dict_ _ except: return 0 Now the example's output concludes with: Example(Marcel) 2 Example(Marcel) 2 Example(Marcel) 2 You might want to do this to simulate the existence of only one instance. The Singleton design pattern has a catchy name, but the wrong focus for most purposes: on object identity, rather than on state. The Borg design nonpattern makes all instances share state instead, and Python makes this a snap. In most cases in which you might think of using Singleton or Borg, you don't really need either of them. Simply define 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's ability to define special methods (for this issue, see Recipe 5.16) The Singleton design pattern is all about ensuring that just one instance of a certain class is ever created. In my experience, it is usually not a good solution to the problems it tries to solve, displaying different kinds of problems in different object models. Typically, what we really want is 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. This alternate pattern to solve roughly the same problems has also been called Monostate. Incidentally, I like to call Singleton "Highlander", since there can be only one. In Python, you can implement Monostate in many ways, but the Borg design nonpattern is often best. Simplicity is its greatest strength. Since the _ _dict_ _ of any instance can be re-bound, Borg rebinds it in its _ _init_ _ to a class-attribute dictionary. Now, any reference or binding of an instance attribute will actually affect all instances equally. I thank David Ascher for suggesting the appropriate name "Borg" for this nonpattern. It's a nonpattern because it had no known uses at the time of publication: two or more known uses are part of the prerequisites for being a design pattern. See the detailed discussion at http://www.aleax.it/Python/5ep.html. The _ _getattr_ _ and _ _setattr_ _ special methods are not involved. You can define them independently for whatever other purposes you want, or leave them undefined. There is no problem either way, since Python does not call _ _setattr_ _ for the rebinding of _ _dict_ _ itself. Also, 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 the statement: _shared_state = {} in class scope so that it doesn't inherit the _shared_state attribute from Borg but rather overrides it. It's to enable this usage that Borg's _ _init_ _ method refers to self._shared_state instead of Borg._shared_state. Borg also works for the new-style classes of Python 2.2, as long as they don't choose to keep state somewhere other than in the instance's _ _dict_ _. For example, Borg cannot support the _ _slots_ _ optimization. However, Borg saves as least as much memory per instance as _ _slots_ _ (the few tens of bytes normally taken up by the instance's nonshared _ _dict_ _), so this isn't really an issue. To Borg-ize a new-style class that inherits from list or dict and keeps state in the items of its built-in base class, you can use automatic delegation, as shown in Recipe 5.9. This technique involves wrapping a classic class around the new-style one and Borg-izing the classic class; I call this idea DeleBorg on http://www.aleax.it/Python/5ep.html. Calling this recipe a Singleton would be as silly as calling an arcade an umbrella. Both may serve similar purposes (letting you walk in the rain without getting wet)�or solve similar forces, in design patterns parlance�but 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. (But Monostate is a design pattern, while Borg is not. And a Python Monostate can exist perfectly well without being a Borg.) For reasons mysterious to me, people often conflate issues germane to Borg and Highlander with others that are really independent, 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 1 instance of that object's class or 23, and whether those multiple instances share state or not. A fruitful approach to problem-solving is known as "divide and conquer," or making problems easier by splitting their different aspects apart. Making problems harder by joining several aspects together must be an example of an approach known as "unite and suffer!" 5.23.4 See AlsoRecipe 5.9 and Recipe 5.22; the article "Five Easy Pieces: Simple Python Non-Patterns" by Alex Martelli (http://www.aleax.it/5ep.html). |
I l@ve RuBoard |