I’ve gradually been evolving a technique of coding in which I put module globals in a class. Recently I stumbled across Norman Matloff’s Python tutorial in which he recommends doing exactly the same thing, and it dawned on me that this technique constitutes a truly idiomatic Python design pattern.
A pattern needs a short, catchy name. I don’t think anyone has yet given this pattern a name, so I will propose the name Globals Class pattern.
I’m sure that many experienced Python programmers are already quietly using the Globals Class pattern. They may not see much point in making a big deal about it, or in giving it a name and decking it out with the fancy title of “design pattern”. But I think a little bit of hoopla is in order. This is a useful technique, and one worth pointing out for the benefit of those who have not yet discovered it. A bit of cheering and arm-waving is in order, simply to catch some attention.
The technique is extremely simple.
- You define a class at the beginning of your module. This makes the class global.
- Then, all of the names that you would otherwise declare global, you specify as attributes of the class.
Really, there is virtually nothing class-like about this class; for instance, you probably will never instantiate it. Instead of functioning like a true class, it functions as a simple container object.
I like to use the name “mem” (in my mind, short for “GlobalMemory”) for this class, but of course you can use any name you prefer.
All you really need is a single line of code.
class mem: pass
That is enough to create your mem container. Then you can use it wherever you like.
def doSomething(): mem.counter = 0 ... def doMore(): mem.counter += 1 ... def doSomethingElse(): if mem.counter > 0: ...
If you wish, you can initialize the global variables when you create the class. In our example, we could move the initialization of mem.counter out of the doSomething() function and put it in the definition of the mem class.
class mem: counter = 0
In a more elaborate version of this technique, you can define a Mem class, complete with methods, and make mem an instance of the class. Sometimes this can be handy.
class Mem: def __init__(self): self.stupidErrorsCount = 0 self.sillyErrorsCount = 0 def getTotalErrorsCount(self): return self.stupidErrorsCount + self.sillyErrorsCount # instantiate the Mem class to create a global mem object mem = Mem()
What’s the point?
So, what does the Globals Class pattern buy you?
1. First of all, you don’t have to go putting “global” statements all over your code. The beauty of using a globals class is that you don’t need to have any “global” statements in you code.
There was a time — in the past, when I still used “global” — when I might find myself in a situation where my code was evolving and I needed to create more and more global variables. In a really bad case I might have a dozen functions, each of which declared a dozen global variables. The code was as ugly as sin and a maintenance nightmare. But the nightmare stopped when I started putting all of my formerly global variables into a global class like mem. I simply stopped using “global” and got rid of all those “global” statements that were cluttering up my code.
So the moral of my story is this. Kids, don’t be like me. I started out using “global” and had to change. I’m a recovering “global” user.
Don’t you even start. Skip the section on the “global” keyword in your copy of Beginners Guide to Learning Python for Dummies. Don’t use “global” at all. Just use a globals class.
2. I like the fact that you can easily tell when a variable is global simply by noticing the mem. modifier.
3. The globals statement is redundant. The Globals Class pattern relieves us of of the burden of having to worry about it.
Python has the quirk that if X is a global, and a function only reads X, then within the function, X is global. But if the function assigns a value to X, X is treated as local.
So suppose that — as your code evolves — you add an assignment statement deep in the bowels of the function. The statement assigns a value to X. Then you have — as a side-effect of the addition of that statement — converted X (within the scope of the function) from a global to a local.
You might or might not want to have done that. You might not even realize what you’ve done. If you do realize what you’ve done, you probably need to add another statement to the function, specifying that X is global. That is sort of a language wart. If you use the Globals Class pattern, you avoid that wart.
4. I think the use of the Globals Class pattern makes the work of static code analyzers (e.g. PyFlakes) easier.
5. The Globals Class pattern makes it possible to create multiple, distinct groups of globals.
This can be useful sometimes. I have had modules that processed nested kinds of things: A, B, and C. It was helpful to have different groups of globals for the different kinds of things.
class memA: pass class memB: pass class memC: pass
6. Finally, the Globals Class pattern makes it possible to pass your globals as arguments.
I have had the situation where a module grew to the point where it needed to be split into two modules. But the modules still needed to share a common global memory. With the Globals Class pattern, a module’s globals are actually attributes of an object, a globals class. In Python, classes are first-class objects. That means that a globals class can be passed — as a parameter — from a function in one module to a function in another module.
Is this really A Good Thing?
At this point I can hear a few stomachs churning. Mine is one of them. Because, as we all know, Global Variables are Always a Bad Thing.
But that proposition is debatable. In any event, it is an issue that I’m not going to explore here. For now, I prefer to take a practical, pragmatic position:
- Sometimes globals are the best practical solution to a particular programming problem.
- For the occasions when Globals are A Good Thing, it is handy to have a way to Do Globals in A Good Way.
So the bottom line for me is that there are occasions when some kind of globals-like technique is the best tool for the job. And on those occasions the Globals Class pattern is a better tool for the job than globals themselves.