singleton ... again

P

Piet van Oostrum

Ben Finney said:
Make that “somewhere†a module namespace, and you effectively have a
Singleton for all practical purposes. So yes, I see the point of it; but
we already have it built in :)

There is a use case for a singleton class: when creating the singleton
object takes considerable resources and you don't need it always in your
program.
 
N

Ned Batchelder

There is a use case for a singleton class: when creating the singleton
object takes considerable resources and you don't need it always in your
program.

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.

Your example is an expensive-to-create object. Why does that mean I
might not want two of them? I can see how it makes sense to have a
factory function, which will make one only when asked, and will hold
onto that object for the next time it's needed.

But that's different than a class which pretends to make instances but
actually always returns the same instance.
 
C

Chris Angelico

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. 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. 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.

ChrisA
 
R

Roy Smith

Chris Angelico said:
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.
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.
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
False
 
E

Ethan Furman

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.

Say you have a database with a column that can only have a handful of values (like an enumeration, for instance) and
this database can have hundreds of thousands of rows. When you're working with all those rows at once having just one
object for the third enum value is a useful optimization.

Say you have a class that represents serial ports or your computer. You should get the same object every time you ask
for SerialPort(2).
 
R

Roy Smith

Ethan Furman said:
Say you have a class that represents serial ports or your computer. You
should get the same object every time you ask
for SerialPort(2).

Why? Certainly, you should get objects which refer to the same physical
port. So:

port_a = SerialPort(2)
port_b = SerialPort(2)

port_a.enable()
assert port_b.is_shutdown() == False

port_a.shutdown()
assert port_b.is_shutdown() == True

But, why do they have to be the same object? Why should I care if

port_a is port_b

is False, as long as all operations I perform on either are reflected in
correct state changes on the other one?
 
E

Ethan Furman

Why? Certainly, you should get objects which refer to the same physical
port. So:

port_a = SerialPort(2)
port_b = SerialPort(2)

port_a.enable()
assert port_b.is_shutdown() == False

port_a.shutdown()
assert port_b.is_shutdown() == True

But, why do they have to be the same object? Why should I care if

port_a is port_b

is False, as long as all operations I perform on either are reflected in
correct state changes on the other one?

You mean use the Borg pattern instead of the Singleton pattern? As far as I can tell they are two shades of the same
thing. Are there any drastic differences between the two? Besides one having many instances that share one __dict__
and the other just having one instance and one __dict__?
 
R

Roy Smith

Ethan Furman said:
You mean use the Borg pattern instead of the Singleton pattern? As far as I
can tell they are two shades of the same
thing. Are there any drastic differences between the two? Besides one
having many instances that share one __dict__
and the other just having one instance and one __dict__?

I envision SerialPort being a thin layer on top of a bunch of
OS-specific system calls to give them a pythonic interface. Things like
is_shutdown() and set_bit_rate() presumably turn into ioctls. No need
to have any state at all beyond a file descriptor.
 
T

Tim Delaney

There is a use case for a singleton class: when creating the singleton
object takes considerable resources and you don't need it always in your
program.

Then have that resource in its own module, and import that module only when
needed e.g. inside a function. Python already has the machinery - no need
to reinvent the wheel.

Tim Delaney
 
C

Chris Angelico

I envision SerialPort being a thin layer on top of a bunch of
OS-specific system calls to give them a pythonic interface. Things like
is_shutdown() and set_bit_rate() presumably turn into ioctls. No need
to have any state at all beyond a file descriptor.

I'd go a bit further. The SerialPort instance would have its own
effective state: the file descriptor. Two of them can be created and
one of them closed, and the fd for that one would be closed while the
other stays open. It's then two objects dealing with a common external
facility.

ChrisA
 
C

Chris Angelico

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.
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.
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

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
 
R

Robert Kern

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.

We don't use `is None` instead of `== None` for the speed. We use it for
robustness. We don't want arbitrary __eq__()s to interfere with our sentinel
tests. If None weren't a singleton that we could use as such a sentinel, we'd
make one.

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
 
C

Chris Angelico

We don't use `is None` instead of `== None` for the speed. We use it for
robustness. We don't want arbitrary __eq__()s to interfere with our sentinel
tests. If None weren't a singleton that we could use as such a sentinel,
we'd make one.

Sure. Yes, its identity is important as part of its being the Python
equivalent of C's null pointer. But the main point of singletons is to
be able to be "instantiated" without creating new elements; the
sentinel status of None is no different from the classic way of
recognizing the presence of an argument:

_SENTINEL = object()
def foo(arg1, arg2=_SENTINEL):
if arg2 is not _SENTINEL: do_stuff_with(arg2)

That's not a singleton in that sense; it's just a unique object. You
could use [] for that instead of object() and it would work just the
same. So None is serving multiple purposes: it's an empty object, but
it's also a sentinel. In many uses, it wouldn't be a problem to have
more Nones floating around, hence it's mostly like your classic
singleton.

My main point about mutable vs immutable is more clearly seen with
integers and strings. Some integers are cached; some strings are
interned; nobody particularly cares about the exact boundaries, except
when playing around with id() or introspection of some sort.

ChrisA
 
G

Grant Edwards

I envision SerialPort being a thin layer on top of a bunch of
OS-specific system calls to give them a pythonic interface.

Yep, that's pretty much what pyserial is

http://pyserial.sourceforge.net/
Things like is_shutdown() and set_bit_rate() presumably turn into
ioctls. No need to have any state at all beyond a file descriptor.

There are OS-dependent things that it's handy to cache in the object
(e.g. a Posix port's current termios settings). It can eliminate a
lot of ioctl() calls if the app spends a lot of time doing things like
messing with modem control lines. The savings in ioctl() calls may
not be worth worrying about, but it's actually simpler/easier to write
that way.

OTOH, caching the termios settings it can cause breakage if two
different processes or port objects are messing with the configuration
of a single port. People who do that are just begging for breakage
anyway, so they get no sympathy from me...
 
G

Gregory Ewing

Steven said:
That does not work. It is trivial to get the type from an instance:

I said *by accident*. Of course it's nearly impossible
to prevent someone who is determined enough from making
another instance, but it will prevent them from doing
so by mistake, if, e.g. they fail to notice the line
in the docs that says "don't try to instantiate this
directly, use the factory function".
 
G

Gregory Ewing

Steven said:
Of course it can happen by accident. It's happened to me, where I've
accidentally called NoneType() (which raises, rather than returning a new
instance).

Well, "unlikely to happen by accident", then.
 
G

Gregory Ewing

Ethan said:
Say you have a class that represents serial ports or your computer. You
should get the same object every time you ask for SerialPort(2).

No, you shouldn't ask for SerialPort(2) at all, you should
call get_serial_port(2). Then you won't be fooled into
thinking that you're creating an independent object each
time.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,078
Messages
2,570,570
Members
47,204
Latest member
MalorieSte

Latest Threads

Top