singleton ... again

Chris Angelico rosuav at gmail.com
Thu Feb 13 15:03:44 EST 2014


On Fri, Feb 14, 2014 at 2:24 AM, Roy Smith <roy at panix.com> wrote:
> In article <mailman.6834.1392292646.18130.python-list at python.org>,
>  Chris Angelico <rosuav at gmail.com> wrote:
>
>> On Thu, Feb 13, 2014 at 10:50 PM, Ned Batchelder <ned at nedbatchelder.com>
>> wrote:
>> > I still don't see it.  To convince me that a singleton class makes sense,
>> > you'd have to explain why by virtue of the class's very nature, it never
>> > makes sense for there ever to be more than one of them.
>>
>> There's a huge difference, btw, between mutable and immutable
>> singletons. With immutables like None, True/False, integers, strings,
>> and tuples thereof, returning a preexisting object is just an
>> optimization. Do it if you want, don't if you don't, nobody's going to
>> hugely care.
>
> People *depend* on None being a singleton (and are encouraged to do so),
> when they use "is" as the test-for-Noneness.

Circular argument, though. If None weren't a singleton, people would
use == to test for Noneness. Since it's been guaranteed to be
optimized to a singleton, the comparison can also be optimized, but
it's still just an optimization, as can be seen with integers. In
CPython, you could test for small integer equality using 'is', but
since that optimization isn't guaranteed, neither is that code
pattern.

>> With mutables, it's hugely different. A singleton
>> "database connection" object would, imo, be hugely confusing; you'd
>> think you created a separate connection object, but no, what you do on
>> this one affects the other.
>
> Except that if you do caching in the database connector, you would
> certainly want the cache to be shared between all the users.  There's no
> right answer here.  Each way has it's advantages and disadvantages.  And
> it would only be confusing if the documentation didn't spell out which
> way it was doing it, leaving people to make (possibly wrong) assumptions.

Compare these:

# Style 1:
import magicsql
import othermodule
conn = magicsql.MagicSQL("127.0.0.1")
conn.set_parameter("foobar", True)
othermodule.do_stuff()
conn.query("select foo from bar")

# Style 2:
import magicsql
import othermodule
magicsql.connect("127.0.0.1")
magicsql.set_parameter("foobar", True)
othermodule.do_stuff()
magicsql.query("select foo from bar")

Suppose othermodule.do_stuff() does the same sort of calls except that
it sets parameter foobar to False. With Style 1, I would expect that
conn is independent of any connection object used by othermodule, and
it would be a major source of bugs (look at PHP's persistent
connections and how extremely careful you have to be with changing
*any* connection settings). But with Style 2, it's obvious that anyone
else importing magicsql and changing parameters will affect me.

That's why module-level functions are the clearer way to do this than
singleton classes.

>> Better there to have module-level functions; at least Python
>> programmers should understand that reimporting a module gives you
>> back another reference to the same module.
>
> Except when it doesn't.  Singleton-ness of modules depends on the names
> under which they were imported.  Symlinks, for example, can fool the
> import machinery.
>
> $ ls -l s1 s2
> lrwxrwxrwx 1 roy roy 1 Feb 13 10:13 s1 -> s
> lrwxrwxrwx 1 roy roy 1 Feb 13 10:13 s2 -> s
>
> $ ls -l s
> total 12
> -rw-rw-r-- 1 roy roy   0 Feb 13 10:13 __init__.py
> -rw-rw-r-- 1 roy roy 101 Feb 13 10:14 __init__.pyc
> -rw-rw-r-- 1 roy roy   9 Feb 13 10:12 singleton.py
> -rw-rw-r-- 1 roy roy 123 Feb 13 10:14 singleton.pyc
>
>>>> import s1.singleton
>>>> import s2.singleton
>>>> s1.singleton == s2.singleton
> False

Sure, they're not a guarantee. And if you fork a subprocess, then you
have copies of the singleton, too. That's not the point here. If you
have two modules and each one types "import random", you would not be
surprised to learn that the name "random" in each is bound to the
exact same random, and that they effectively share state. If you're
calling random.random(), and then you call another module which
imports random and calls random.random(), you don't complain that your
sequence of random numbers was disrupted.

But if you were to get yourself a random.Random() instance and someone
else does the same, then the style of code makes it *look like* you
should be working with independent objects. And in the case of the
random module, that is exactly what happens. Instantiate? Separate.
Module-level functions? Shared.

ChrisA



More information about the Python-list mailing list