With all its upsides, Python has a problematic global state story; it's easy to shoot yourself in the foot.
Assuming Python-like modules, module-level globals are worse for encapsulation.
1. A function can access module-level state without having been explicitly passed a reference to it.
2. You can't create additional instances of module-level state. For example, if your app server uses a module-level global for the server listening socket, you can't run two app server instances in the same process.
So a global variable should not be used for such cases. That doesn’t mean it is inherently bad. People writing the code are supposed to think about what constructs to use for a given scenario.
Yes, programmers have to be aware of what they're doing, but that's true of any programming construct. It doesn't mean that we shouldn't identify the dangers.
Global mutable variables are also awful for multithreading. Thread-locals help, but even they breaks horribly as soon as you use something like a thread pool.
Same can be done with module level globals.
There's another point in favor of instance variables: it gives you the flexibility of multiple values in different parts of your code. As codebases grow, the danger of two unrelated places wanting different values for your global also grows.
Say a physics simulation has a global for gravity. This precludes, or at least unnecessarily complicates, the same process from having two separate simulations with different gravities (perhaps in different threads). This is especially bad in library code.
To be clear, I am not saying globals should be used everywhere. There are a programming construct just like any other. When used carefully, they can greatly simplify code — but also have the potential to horribly complicate the code when abused. And that is true about any programming construct.
One thing that irritates me is the tendency to paint things black and white — eg. goto, globals, multiple inheritance etc. are bad. So “modern” programming languages try to eliminate them in a misguided attempt to keep programmers safe from themselves. I agree that these things are often misused, but a bad programmer will figure out a way to misuse anything — or do you think it is impossible to write bad code in something like, say, Go? Dumbing things down doesn’t prevent bad programmers from messing up, instead it causes the rest to write horrible code because of the lost power.
Is making them properties and sticking them in a "GlobalVariablesByAnotherName" singleton and chucking that around better? Possibly. Possibly not.
I have found, however, that any code where someone has used global variables is hard to share with other developers. Maybe that's the crux of the matter.
Used to be you wouldn't use globals because it was difficult to figure out what code accessed or changed their values. Now that's a right click away.
> As with all HeuristicRules, this is not a rule that applies 100% of the time. Code is generally clearer and easier to maintain when it does not use globals, but there are exceptions.
For me, the root of the problem with them is that they introduce coupling (or to put it another way, they break modularity). When you introduce global state, you introduce it everywhere. In reality there are probably only a handful of places that it relates to, but "is the global state involved here?" becomes a question you need to ask yourself whenever you write/update any part of the code.
The same argument can apply at more fine-grained levels. If you introduce a module-level variable, you need to keep it in mind whenever dealing with any code in that module. Similarly for class-level and instance-level variables.
There is a trade-off: ease-of-access and clarity vs the extra cognitive load you are introducing to every part of the scope. In my opinion, that balance determines whether a variable is good or bad. For any non-trivial piece of code, the sum almost always works out on the side of reducing the cognitive load.
If module global variables are considered truly horrible, the same can be said about instance variables inside classes. They are as good (or bad) as module globals in the sense that they represent shared state and can potentially be mutated by any method/member function in the class.
Of course the functional programmers among us will say that shared state of any kind is bad, and I agree. But we are not talking about that here, are we?