Re: if not global -- then what?

On Sat, 20 Feb 2010 11:25:46 -0800, egasimus wrote:

Hi, newbie here. I've read on using the 'global' keyword being
discouraged; then what is the preferred way to have something, for
example a class containing program settings, accessible from everywhere,
in a program spanning multiple files?

Such a thing is no different from keeping many global settings. It's not
the global KEYWORD that is discouraged, but the over-use of global
variables itself.

See, for example:

In a nutshell: shared variables introduce tight coupling between parts of
your code that should be independent. Whether they are truly global, or
passed around in a Settings object, makes little or no difference. In
both cases, the risks and problems are identical.

Let me give a fanciful example: suppose your refrigerator, oven and
shower all shared a single global temperature setting instead of having
three local temperature settings. This would be a recipe for disaster:
every operation to any of the three would need to carefully save the
current temperature setting, and there's the constant risk of scalding
showers, ruined foods, and undercooked meals.

Having three different global temperature settings helps a bit, but that
just expands the number of variables that everything has access too. Why
do the television and the garage door need access to any temperature
setting, let alone all three?

Python globals aren't as bad as in some other languages, because "global"
means global to a single module, not global to the entire program, but if
you create a Settings object and import it from module to module, you
break the encapsulation, *and* you then have the problem of needing to
make sure it is initialised properly.

Global constants also aren't as bad, because the coupling is weaker.
Unfortunately Python doesn't have constants, except by convention, so
there's always the risk that some badly-behaved function might decide to
redefine (say) math.pi to 3.15.

Unfortunately, you probably can't get rid of globals altogether, but the
tricks are (1) use as few of them as possible, and (2) make them local as
quickly as possible.

For example, you might have something like this:

default_header = "My special header" # A global

for option, argument in command_line:
if option == "--header":
default_header = argument

class Printer:
def print_page(self, text, header=None):
if header is None:
header = default_header
print header
print text

I would limit the scope of default_header so that it was local to the
printer class:

for option, argument in command_line:
if option == "--header":
Printer.default_header = argument

class Printer:
default_header = "My special header"

def print_page(self, text, header=None):
if header is None:
header = self.default_header
print header
print text

You might not be able to completely eliminate either globals, or a
Settings object, but you should be able to make it much, much smaller.

In practice, to make testing easier, I'd split the command line
processing into two steps: a function that reads and processes the
command line into a single local settings object, then a separate
function that applies those settings to the various classes. The function
that reads the command line doesn't need to know where the settings
eventually end up, and the function that applies the settings doesn't
need to know where they come from. All the coupling between settings and
classes is in one place, the second function, instead of scattered all
over your code.