When to use globals
I am dogmatic about never using global variables. But when people way smarter than me use them, like Brian Kernighan does when he builds a command-line interpreter in that chapter of The Unix Programming Environment, I wonder if maybe I’m being too reluctant.
I was looking at a python module vaguely like this:
def foo():
do_foo_stuff()
def bar():
do_bar_stuff()
def baz():
do_baz_stuff()
def quux():
do_quux_stuff()
I had a bunch of independent functions. I wanted to add logging. I saw two easy ways to do it:
def foo():
logger = get_logging_singleton()
logger.log_stuff()
do_foo_stuff()
def bar():
logger = get_logging_singleton()
logger.log_stuff()
do_bar_stuff()
def baz():
logger = get_logging_singleton()
logger.log_stuff()
do_baz_stuff()
def quux():
logger = get_logging_singleton()
logger.log_stuff()
do_quux_stuff()
In the above code, I would get a reference to my logger object in each function call. No globals. Maybe I am violating some tenet of dependency injection, but I’ll talk about that later. Anyhow, the point I want to make is that the above approach is the way I would do it in the past.
Here’s how I decided to write it this time:
logger = get_logging_singleton()
def foo():
logger.log_stuff()
do_foo_stuff()
def bar():
logger.log_stuff()
do_bar_stuff()
def baz():
logger.log_stuff()
do_baz_stuff()
def quux():
logger.log_stuff()
do_quux_stuff()
All the functions access the logger created in the main namespace of the module. It feels a tiny bit wrong, but I think it is the right thing to do. The other way violates DRY in a big fat way.
So, a third option would be to require the caller to pass in the logging object in every function call, like this:
def quux(logger):
logger.log_stuff()
do_quux_stuff()
This seems like the best possible outcome — it satisfies my hangup about avoiding global variables and the caller can make decisions about log levels by passing any particular logger it wants to.
There’s two reasons why I didn’t take this approach:
- I was working on existing code, and I didn’t have the option of cramming in extra parameters in the calling library. So, I could do something like
def quux(logger=globally_defined_logger)but I’m trying to make this prettier, not uglier. The whole reason that I wanted to add logging was that I wanted some visibility into what what the heck was going wrong in my app. I didn’t have time to monkey with overhauling the whole system. - I plan to control my logger behavior from an external configuration system. I don’t want to change code inside the caller every time I want to bump the log level up or down. It is the conventional wisdom in my work environment that I face less risk just tweaking a configuration file setting and restarting my app rather than editing my code*.
[*]I suspect that in the final analysis, this belief will be exposed as garbage. But for right now, it seems pretty true that bugs occur more frequently after editing code than after editing config files.
UPDATE: Apparently, I’m not just talking to myself here! Gary Bernhardt linked to this post and added some really interesting points. Also, his link to the post on the origin of the phrase now you have two problems was something I hadn’t heard of before.
Mike Pirnat said,
August 24, 2007 @ 9:54 pm
Or there’s always:
class Frotz(object):
def __init__(self, logger=None):
self.logger = logger or logging.getLogger()
def foo(self, ...):
self.logger.log(...)
...
def bar(self, ...):
self.logger.log(...)
...
Which would seem to be a little happier on the DI front and the cleanliness front. And then your module doesn’t get all fussy about importing if for some reason it can’t get its logger going.
Though in reality I don’t think you should be swearing off globals entirely; I think perhaps the message has gotten distorted over time. If you have a really good reason, and you know what you’re doing, then it’s probably OK to have the occasional global. I suspect that we mostly shout about the badness of globals so that the first-year comp sci kids get the message that they should pay attention and not start using globals all willy-nilly.
Mike Pirnat said,
August 24, 2007 @ 9:55 pm
Awesome, your blog ate my nice PRE tags, so no properly indented Python code for you (boo hoo).
matt said,
August 25, 2007 @ 12:50 pm
Thanks for the comment. I think I’m one of those people that got yelled at for using globals and now I have a hangup about them.
Anyhow, you can use the code tag and it will respect whitespace.