On Sun, 19 Sep 2010 02:50:42 am Knacktus wrote: > Hey all, > > the usual explanation for the usage of a Singleton goes like this: > > "Use a singleton if you want to make sure, that only one instance of > a class exists." > > But now I ask myself: Why should I call the constructor of a class > more than once if I only want one instance?
Why does int("spam") raise an exception that you can catch, instead of dumping core and crashing the machine? After all, if you don't want to crash the machine, just make sure you never call int() with something that isn't an integer. Right? No. We all know that no matter how much you intend to only call int() on strings which actually do represent integers, bugs do happen, so we have the language protect us from our mistakes. Likewise, you might intend to only call the constructor of a class once, and in a 200 line script you might even be confident of that fact, but in a 200,000 line program how confident will you be? You might say "do a search of the source code, and if you see the constructor called twice, you know it's wrong" but of course there might be a legitimate reason to call the constructor from different places in the code, so long as only one of them is actually executed. > After all, I decide in my code when to create an instance or when to > pass an existing instance around. Why should you have to decide? Why bother to track that yourself, when the class could track it for you? Depending on your needs or philosophy, one tactic is to have the class explicitly tell you when you're trying to initialise a second instance, by raising an exception. Python does that with some singletons: >>> type(None)() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: cannot create 'NoneType' instances And the same for NotImplemented. Personally, I don't understand that tactic. I prefer the tactic used by the pair of singletons True and False: >>> bool() is False True No matter how many times you call bool(), it will always return one of the two singleton instances True or False. Imported modules are the same -- every time you import a module, Python gives you the same module instance. So if you need singleton behaviour, a module gives you that for free. > Example in pseudocode: > > class Session(object): > """Hold a dictionary of ident_to_data_objects""" > > def __init__(self, ident_to_data): > self.ident_to_data = ident_to_data > > Now, that would be a typical "singleton" use case. I want one > instance of this class application-wide. For example in a View class: > > class View(object): > """Create fancy views""" > > def __init__(self, session): > self.session = session If there's only ever one session, can't possibly be a second, then why make it an attribute of the view instance? You might have 30 views, so why pretend that they could have 30 different sessions? Better to inject the session into the View class itself, as a class attribute: class View(object): """Create fancy views""" session = None def __init__(self, session=None): if self.session is None: self.__class__.session = session elif session is not None: raise ValueError('cannot use two sessions') But why pretend that you might have multiple View-like classes, with different sessions? Since you have the invariant that there can only ever be one session, you can't allow this: class MyView(View): session = another_session() Since that's prohibited, don't pretend it's allowed. You might as well make session a global variable, and stop pretending to be more virtuous than you actually are. And that's the problem -- singletons are often (not always, but often) just a sop to the idea that global variables are bad. They are, but disguising them as a singleton object doesn't make them less bad. It's better to think really hard about *why* you want only one instance of a class, whether it's really necessary, and if so, whether an alternative such as the Borg pattern would be better. http://code.activestate.com/recipes/66531/ > In my code I use these classes like this: > > class MainApp(object): > """Do some stuff with the data_objects""" > > def __init__(self): > self.session = Session() > self.view = View(self.session) Since every MainApp instance creates it's own independent session object, no, there's no singleton. It's not a singleton if you merely *happen* to only create one instance. It's only a singleton if you *can* only create one instance (or at least, one instance with each specific value). E.g. you can create multiple lists with the same value but different identity: >>> a = [1, 2] >>> b = [1, 2] >>> a is b False so lists are not singletons. But you can't create multiple module objects with the same value and different identity: >>> import math >>> import math as another_math >>> math is another_math True So even though there is are many different modules, we call them singletons. > Would a singleton usage in the View class look like that? > > class View(object): > """Create fancy views""" > > def __init__(self): > self.session = Session() Again, there's nothing "singleton" about this. Apart from modules, you don't get singletons for free in Python, you have to work at it. One way is this: # Untested class Singleton(object): _instance = None def __new__(cls): if cls._instance is not None: # raise TypeError('cannot make a second instance') # but this might be better: return cls._instance else: inst = object.__new__(cls) cls._instance = inst return inst > What's the point? Good question. It seems to me that the Singleton design pattern has the wrong focus, and so it actually isn't useful or necessary in most of the cases that people use it. In my opinion, it's mostly a gimmick. Some exceptions -- the None singleton is useful, because we *do* care about identity. None has no state, it has no attributes or useful methods, the *only* thing we care about is identity -- something either is None or it isn't. Same with NotImplemented, True, False and a handful of other objects. So Singleton is valid for them. Modules, on the other hand, could just have easily be implemented as the Borg pattern -- you might have two, three, a thousand instances of the math module, so long as they all share the same state rather than being copies, it doesn't matter. Ensuring that there's only one instance of the math module is a tiny memory optimization, but other than that, it would make no real difference. > Is it the spared typing when instanciating a lot of > View classes (I wouldn't need to pass the session to the > constructor). Or are there some more advantages (instead of passing > the same instance aorund)? Also, what would you guys consider as > disadvantages? The biggest disadvantage of singletons is that they're often just disguised global variables. Instead of writing: # Globals default_page_size = "A4" default_left_margin = 2 # cm default_right_margin = 2 # etc. people have a tendency to do this: MyApp(defaults=SingletonPageSettings("A4", 2, 2)) and think this makes their code "better". (I blame the Go4 for making a religion out of design patterns which exist only to work around Java's limitations.) But it's just a disguised global record, with exactly the same problems as ordinary globals. The second biggest problem is that, invariably, if you think something needs to be a singleton today, tomorrow you'll wish you had two of them. You have a singleton Desktop object in your window manager, and then you realise that there's no reason not to have multiple desktops. Your word processor has a singleton Printer config, but then you buy a second printer. Even if you really do want a single instance, say of your MainApp object, sometimes it's useful to have a second one for debugging. So in Java land, programmers often try to bypass the singleton code so they can defeat the original designer who made it a singleton. > Another question related to this topic is, if I would use a module as > a singleton (as suggested by Steve and other), how would I import it > the instances of a class? Maybe like this? > > class View(object): > """Create fancy views""" > > import session > > def do_something(self, ident): > self.certain_data_object = session.ident_to_data[ident] Almost -- you would have to refer to self.session, not just session. Other than that, you could do it, of course, but it's simpler to stick with this: import session class View(object): """Create fancy views""" def do_something(self, ident): self.certain_data_object = session.ident_to_data[ident] -- Steven D'Aprano _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: http://mail.python.org/mailman/listinfo/tutor