I have observed these "design pattern shoehorned into Python" so many times ... Great post. When you see these things done in a code base, you know that you got people, who would rather want to write Java working on it. Or maybe people who don't have the feel for Python as a language or something.

First thing I looked up in the article was "singleton", as a sanity check, whether the article is any good. And yes, it shows module level binding as alternative, exactly what I expected, because I looked into this in the past, when someone implemented API client singleton, in a case of irrelevant early optimization of something that was never a bottleneck.

Articles like this are helpful in spreading the awareness, that one should not hold a Python like one holds a Java.

Best practices in software engineering seem to usually pertain to a particular language or set of languages. I've also noticed that authors usually don't notice that this is the case.

In fact, I've pissed off some people in interviews for holding this view. We aren't really that empirical as an industry about best practices.

Exactly. Best practices, especially patterns are language dependent. I mean, it already starts with decorators. Even someone slightly familiar with Python will know, that Python has something called decorators. So how to use them? Which practices that people have in Java or similar language do they replace, make unnecessary? Programming is not a "one size fits all" kind of thing.

Module-level initialization has one huge problem in Python, though.

That means that as soon as you import a module, initialization happens. Ad infinitum, and you get 0.5s or more import times for libraries like sqlalchemy, requests...

> Ad infinitum

The result of module import is cached (which is also what makes it valid to use a module as a singleton); you do not pay this price repeatedly. Imports can also be deferred; `import` is an ordinary statement in Python which takes effect at runtime rather than compile time.

Modules that are slow to import are usually slow because of speculatively importing a large tree of sub-modules. Otherwise it's because there's actual work being done in the top-level code, which is generally unavoidable.

(Requests is "only" around a .1s import on my 11-year-old hardware. But yes, that is still pretty big; several times as long as the Python interpreter plus the default modules imported automatically at startup.)

Initialization only happens once, when you import the module for the first time, afaik. Unless you are running multiple Python processes, that is.

yep, the legacy codebase I maintain does a lot of this kind of stuff and has made it difficult to write unit tests in some cases due to all the code that runs at import and all the state we end up with

The article addresses this.

I know, I'm just complaining about the mountain of code that does this at my company. And there is no fixing it using the article's approach or any other for that matter due to the sheer scale of the abuse.