Globals or objects?

M

MartinRinehart

I had a global variable holding a count. One source Google found
suggested that I wouldn't need the global if I used an object. So I
created a Singleton class that now holds the former global as an
instance attribute. Bye, bye, global.

But later I thought about it. I cannot see a single advantage to the
object approach. Am I missing something? Or was the original global a
better, cleaner solution to the "I need a value I can read/write from
several places" problem?
 
S

Steve Holden

I had a global variable holding a count. One source Google found
suggested that I wouldn't need the global if I used an object. So I
created a Singleton class that now holds the former global as an
instance attribute. Bye, bye, global.

But later I thought about it. I cannot see a single advantage to the
object approach. Am I missing something? Or was the original global a
better, cleaner solution to the "I need a value I can read/write from
several places" problem?

Look up "coupling" and "cohesion" in Wikipedia or similar, and you will
find out that global variables are bad because they introduce tight
coupling between different pieces of functionality in your code.

If a function uses a global variable then you have to initialize the
same global variable in another program that uses it: yet another piece
of setup you will forget to do.

Having said that, where are you storing the reference to the singleton
instance? It wouldn't be a global, would it?

regards
Steve
 
H

Hrvoje Niksic

Steve Holden said:
If a function uses a global variable then you have to initialize the
same global variable in another program that uses it: yet another
piece of setup you will forget to do.

If the global variable belongs to the module, then it is up to the
module to initialize it. A different program program will simply
import the same module and automatically get the same variable,
properly initialized.

One reason why a singleton's attribute may be better than a global
variable is that it's easier to later add a callback when the value is
changed without breaking the interface. A module cannot react to
module.somevar = value; on the other hand, a singleton can implement
__setattr__ that reacts to module.singleton.attr = value.
 
A

Aahz

I had a global variable holding a count. One source Google found
suggested that I wouldn't need the global if I used an object. So I
created a Singleton class that now holds the former global as an
instance attribute. Bye, bye, global.

But later I thought about it. I cannot see a single advantage to the
object approach. Am I missing something? Or was the original global a
better, cleaner solution to the "I need a value I can read/write from
several places" problem?

The advantage of the global singleton is that it is a container;
therefore, its contents are mutable and you don't need to keep using the
``global`` statement.
 
T

tinnews

Steve Holden said:
Look up "coupling" and "cohesion" in Wikipedia or similar, and you will
find out that global variables are bad because they introduce tight
coupling between different pieces of functionality in your code.
I think the OP was asking rather how the Object is any different from
the global if it's simply used as a wrapper for a single value.

In usage, philosophy or whatever it's effectively identical and
introduces all the same problems as a global does, it just gives it a
longer name.

Objects only become useful when the encapsulate more than on thing.
 
T

tinnews

Aahz said:
The advantage of the global singleton is that it is a container;
therefore, its contents are mutable and you don't need to keep using the
``global`` statement.

..... but you do keep having to use a longer reference to the value so
what have you won?
 
D

Duncan Booth

I had a global variable holding a count. One source Google found
suggested that I wouldn't need the global if I used an object. So I
created a Singleton class that now holds the former global as an
instance attribute. Bye, bye, global.

But later I thought about it. I cannot see a single advantage to the
object approach. Am I missing something? Or was the original global a
better, cleaner solution to the "I need a value I can read/write from
several places" problem?

A singleton is simply another form of global. The benefit of a singleton
is that it can be used to encapsulate several related values and methods
in a single globally accessible space. The easiest way in Python to
implement a singleton is just to use a module: all modules are
singletons and there is a defined mechanism (import) for accessing them.

It sounds as though you simply replaced a value stored in a singleton
object (global in a module) with a value stored in a singleton object
(attribute of your class) which is referenced from a singleton object
(your module).

The real benefit from using an object comes when you stop making it a
singleton. Create it at some high level in your code and pass it around
as a parameter, or hold a reference to it in some other object which is
passed around. When you do this you no longer have a global, and the
benefits you get include:

* for your counter example, you can have multiple counters counting
different things without having to duplicate counter code for each thing
you want to count.

* it becomes much easier to write tests for your code, because each test
can create its own context and be completely independant from the other
tests. e.g. you can test a generic counter without having to know you
are testing the foo counter or the bar counter, and you can test
something which counts foos without having to worry that other tests may
already have counted some foos.

There is another lesser (and Python specific) benefit to storing a value
as an attribute of a class rather than a global in a module: if you
later want to intercept assignments to the attribute you can turn it
into a property, but doing the same thing on a module is much harder.
 
S

Steven D'Aprano

Clarity - why is it clearer?

Consider two function calls:


x = ham(arg, counter)
y = spam(arg)

Both do exactly the same thing: ham() takes an explicit "counter"
argument, while spam() uses a global variable. Which one makes it clear
that it uses a counter, and which does not?

Simplicity - no, you've added an extra layer.

Consider trying to run ham() and spam() twice, independently:

x1 = ham(arg, counter)
x2 = ham(arg, another_counter)

y1 = spam(arg)
saved_counter = counter # save the global variable
counter = 0 # reset it to zero
y2 = spam(arg)
another_counter = counter
counter = saved_counter


Which is simpler?


Robustness - how?

If you avoid the use of globals, this code will work as expected:

x = ham(arg, counter)
assert counter.n = 5 # or whatever value ham() sets it to...
function()
another_function()
yet_another_function()

At the end of this block of function calls, you can be confident that
counter still has the same value.

Now consider using globals:

x = spam(arg)
assert counter = 5 # or whatever value spam() sets it to...
function()
another_function()
yet_another_function()

What value will counter have? The only way to tell is to carefully read
through function(), another_function() and yet_another_function() and see
whether or not they modify the global counter. Maybe they do, maybe they
don't, who can tell?
 
M

MartinRinehart

A fascinating, well-informed discussion. Thanks to all.

Holden's suggestion re coupling and cohesion was most informative. I
conclude that whether you use an object or a global (within a module,
not across modules) is an implementation detail that has no impact on
either cohesion or coupling.

D'Aprano's discussion is persuasive but only in the case where you do
not want multiple actors updating a single value. In my case multiple
actors have legitimate interest in updating the value. (Actors within
a single thread, fortunately.)

I conclude that intra-module globals are NOT always evil. They get a
bad rap because some of them are evil.

Anecdote proving nothing: My count got wrapped into an object. I found
additional uses for the object, so it stayed there. Finally, the count
and the object got designed out of the module. RIP.
 
T

tinnews

Steven D'Aprano said:
Consider two function calls:


x = ham(arg, counter)
y = spam(arg)

Both do exactly the same thing: ham() takes an explicit "counter"
argument, while spam() uses a global variable. Which one makes it clear
that it uses a counter, and which does not?
But you're not comparing what the OP posted. He was comparing a
global with an object with a single variable inside it. Either would
work with the y = spam(arg) example above.

I agree absolutely about the reason for not using globals but that
applies similarly to objects. The OP was comparing a global integer
variable with an object with a single integer variable in it.
Consider trying to run ham() and spam() twice, independently:

x1 = ham(arg, counter)
x2 = ham(arg, another_counter)

y1 = spam(arg)
saved_counter = counter # save the global variable
counter = 0 # reset it to zero
y2 = spam(arg)
another_counter = counter
counter = saved_counter

Which is simpler?

Again it's not what the OP was doing. I again agree absolutely that
in general globals are a 'bad thing' if you can avoid them but your
example doesn't really affect the original OP question.
If you avoid the use of globals, this code will work as expected:

x = ham(arg, counter)
assert counter.n = 5 # or whatever value ham() sets it to...
function()
another_function()
yet_another_function()

At the end of this block of function calls, you can be confident that
counter still has the same value.

Now consider using globals:

x = spam(arg)
assert counter = 5 # or whatever value spam() sets it to...
function()
another_function()
yet_another_function()

What value will counter have? The only way to tell is to carefully read
through function(), another_function() and yet_another_function() and see
whether or not they modify the global counter. Maybe they do, maybe they
don't, who can tell?
You're just telling me all the problems one can have with globals
which I know and agree with. But an object with a single variable in
it has exactly the same issues.
 
S

Steven D'Aprano

D'Aprano's discussion is persuasive but only in the case where you do
not want multiple actors updating a single value. In my case multiple
actors have legitimate interest in updating the value. (Actors within a
single thread, fortunately.)

You missed the point. It's not whether you only have *one* actor has to
update the value, or more than one. It's about having loose coupling
between the actors and the value.

Consider a thought experiment. Suppose somebody wrote a math library that
worked something like this:

# contents of maths.py
x = 0.0 # default value for global x

def sin():
taylor = x + x**2/2 + x**3/6
# Taylor's expansion of sine. (I think.)
return taylor


and so forth. To use this library, you would have to do this:

savex = maths.x
maths.x = 0.1
y = maths.sin() # multiple actors with a legitimate need
z = maths.cos() # to access a single global value
maths.x = savex


The functions are tightly coupled to x, and can't operate on anything
else other than x, so your program ends up being filled with wasteful
code storing the value of x, setting it to a value, then restoring it.

I think we will all agree that the above is a ridiculous way to write
code. But what you're doing differs from it only in degree, not kind: all
you're doing is replacing x with counter.

In your example, you say you have "multiple actors [with a] legitimate
interest in updating the [global] value." That's fine. But that *group*
of actors still behaves as a single entity. What happens when you have
two *groups*?

savecounter = counter
foo(actor1) # All these actors are in the same group:
foo(actor3) # "Odd" actors.
foo(actor5)
print counter
counter = savecounter
# But these are in a different group:
foo(actor2) # "Even" actors.
foo(actor4)

As soon as you have multiple groups, globals become a millstone around
your neck because of that tight coupling: your function foo() can't
operate on any value except counter. That makes testing very hard,
because your test functions can't provide their own counters.

Now, there are times where using globals is acceptable *in spite of* this
problem. For example, I will often use globals to write a piece of quick-
and-dirty through-away code. Or as a first draft: write a bit of code
using a global, get the basic algorithm working, then modify it to work
with a parameter instead. Or perhaps you've decided that you really
*want* that tight coupling. And you can't avoid globals altogether,
because there's always going to be a global scope.
 
S

Steven D'Aprano

But you're not comparing what the OP posted. He was comparing a global
with an object with a single variable inside it. Either would work with
the y = spam(arg) example above.

What do you mean by "an object with a single variable inside it"? I don't
understand what that is supposed to mean, or why you think it is the same
as a global. Do you mean a Singleton?

If so, then the answer is simple: using a Singleton argument instead of a
global is better, because with a global you are stuck to always using the
global (at least until you can re-write the code), but with the Singleton
argument, you may be enlightened and *not* use a Singleton.
 
T

tinnews

Steven D'Aprano said:
What do you mean by "an object with a single variable inside it"? I don't
understand what that is supposed to mean, or why you think it is the same
as a global. Do you mean a Singleton?

If so, then the answer is simple: using a Singleton argument instead of a
global is better, because with a global you are stuck to always using the
global (at least until you can re-write the code), but with the Singleton
argument, you may be enlightened and *not* use a Singleton.
But if you stop using the Singleton the code no longer does the same
as it would with a global does it?

As I keep saying I agree wholeheartedly with the general idea that
globals are a bad thing. However wrapping up what is effectively a
global in a different construct doesn't seem to me to be any help at
all. What you need to do is take a long hard look at the global and
decide if there are better ways of doing it, not just simply wrap it
up in a class that really doesn't help at all.
 
S

Steven D'Aprano

But if you stop using the Singleton the code no longer does the same as
it would with a global does it?

That's a *good* thing, not a problem. The whole idea is to get away from
the bad behaviour of globals, not find some other way to implement it.
 
T

tinnews

Steven D'Aprano said:
That's a *good* thing, not a problem. The whole idea is to get away from
the bad behaviour of globals, not find some other way to implement it.
That's *exactly* what I was saying in the bit you snipped! :)

However it doesn't answer the OP's questionas to why he was advised to
replace a global with a class *without* changing the way it was used.
 
S

Steve Holden

Steven said:
That's a *good* thing, not a problem. The whole idea is to get away from
the bad behaviour of globals, not find some other way to implement it.
I think that advocation of a global singleton is not really solving the
problem. Not the problem I perceive, anyway. From your comments it's
difficult to see whether we share the same perceptions, so let me elucidate.

The issue I have with globals is primarily that use of a global
introduces a tight coupling between a function and its environment,
since any environment that uses the function has to provide the global.

It's true that a global singleton is a half-way step to a solution of
the problem because it portends the true solution, which also solves the
problem of multiple counts. That solution, of course, is to have the
"function" become a method of some counter object, which can then be
used to count many things.

In other words (untested, so ignore the many Holden types that this will
inevitably incur):

--------
bad.py:

counter = 0

def count(n):
global counter
counter += n
--------
justasbadifnotworse.py:

class bunch: pass;

counter = bunch()
bunch.count = 0

def count(n):
counter.count += n
--------
better.py:

class Counter:
def __init__(self):
self.count = 0

counter1 = Counter()
counter2 = Counter()

def count(n, counter):
counter.count += n
--------
best.py:

class Counter:
def __init__(self):
self.count = 0
def count(self, n):
self.count += n
--------

Now the names I have chosen are pejorative, but this is typically the
development you see in someone's programming style as they slowly (or
not slowly) start to understand the value of the object-oriented approach.

Unfortunately this sometimes goes too far, and people end up writing
"monster objects", with dozens of methods and lots of instance variables
used to communicate between them. With that style of programming the
instance variables introduce the same tight coupling between the methods
that globals do in a less object-oriented style, and the code becomes
just as difficult to understand. Only now there can be multiple
instances of the badly-written function!

Sometimes this is inevitable, but good programming style is about trying
to strike the right balance between contexts. It is truly possible to
write good and bad programs in any language you like, and rules like
"globals are bad" need to be taken in context just like any other rule.

After all, some things *have* to be global for our programs to make any
sense at all, unless you want to adopt a truly functional style that has
never appealed to me. I like my programs to have state.

regards
Steve
 
S

Steven D'Aprano

I think that advocation of a global singleton is not really solving the
problem. Not the problem I perceive, anyway. From your comments it's
difficult to see whether we share the same perceptions, so let me
elucidate.

Thanks for the details. I do agree with you. It wasn't me that suggested
that the OP create a global singleton, or that using such a beast was a
good plan. I hope that my response that [paraphrasing] a global singleton
was good because it lets the developer STOP using a global singleton can
be seen in context of tinnews badgering me again and again to explain why
using a global singleton is better than an old-style global variable.
That's not a position I would take.

[...]
After all, some things *have* to be global for our programs to make any
sense at all, unless you want to adopt a truly functional style that has
never appealed to me. I like my programs to have state.

Some things are global, yes, but very few of them need to be "global
variables" in the sense we're discussing. Actually, in the sense of the
OP's example, I don't believe anything *needs* to be a global variable,
unless it's a deliberate design choice (e.g. a global flag controlling
some aspect of program behaviour, which I see as a legitimate if
restrictive choice).
 

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
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top