$ python -c 'import this' | grep way
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
There are many layers to this, but the most important thing to point out is that having only one obvious way is just a preference (or ideal). In practice, any deliberate attempt to prevent something logical from working is counter-productive, and there is really no way to control what other people think is or isn't "obvious". And we all sometimes just expect things to work very differently than they actually do, even in ways that might seem bizarre in retrospect. We can't all "be Dutch" all the time.
But let me dig into just one more layer. Pay attention to the hyphens used to simulate em-dashes, and how they're spaced, versus what you might think of as "obvious" ways to use them. I'm assured that this is a deliberate joke. And of course, now that we have reasonably widespread Unicode support (even in terminals), surely using an actual emdash character is the "obvious" way. Or is it? People still have reasons for clinging to ASCII in places where it suffices. Then consider that this was written in 2004. What was your environment like at that point? How old was Unicode at that point? What other options did you have (and which ones did you have to worry about) for representing non-ASCII characters? (You can say that all those "code pages" and such were all really Unicode encodings, but how long did it take until people actually thought of them that way?) On the other hand, Python had a real `unicode` type since 2.0, released in 2001. But who do you know who used it? On yet another hand, an emdash in a terminal will typically only be one column wide (just as an 'm' character is), and barely visually distinct from U+002D HYPHEN-MINUS. (And hackers will freely identify "dash" with this character, while Unicode recognizes 25 characters as dashes: https://www.compart.com/en/unicode/category/Pd) Reasonable people can disagree on exactly when it should have become sensible to use actual emdashes, or even whether it is now. Or on whether proper typography is valuable here anyway.
I really appreciate how this article explains why certain design patterns became a thing. Usually, it was to address some very practical problem or limitation. And yet, a lot of younger programmers treat these patterns like a religious dogma that they must follow and don't question if they really make sense for the specific situation they are in.
The main motivation for the concept of design patterns is to give unique names to existing programming patterns, so that when someone says “Strategy pattern”, everyone knows what pattern that refers to, and vice versa that the same pattern isn’t called ten different things. It’s to make communication about program design efficient, by defining a vocabulary of patterns that tend to reoccur, and a common structure for describing them. Not all patterns were successful in that way, but it’s the main idea behind design patterns.
The question of when a using a given pattern is appropriate is orthogonal to that. The fact that a named pattern has been defined doesn’t imply a recommendation to use it across the board. It depends on the context and on design forces, and those change with time and circumstances. Anti-patterns are patterns as well.
It’s a pity that the idea of design patterns ended up (after the pattern language craze faded) being almost exclusively associated with the specific patterns named and described in the GoF book.
You can implement a singleton class that works properly quite easily. The advantage is that most people are familiar with singleton as a pattern, and it is a self contained chunk of code. The cache solution you provided works, but its functionality is not obvious and it feels very hacky to me. Somebody's going to initialize Whatever in another way down the line without using the cached function...
I was writing a python thing where the class was going to have like at least 20 paramaters to configure it. Builder pattern was kind of feeling like a good idea to keep it cleaner for the user. But it is surprising to see in the python world. It felt like a mess of default values though for the user to handle.
All good ones. I'll add this because A: It's common in Python, and B: There are suitable alternatives in the standard library:
Conflating key/value lookups (dicts) with structured data (classes). They are both useful tools, but are for different purposes. Many python programmers (Myself many years ago included!) misused dicts when they should have been using dataclasses.
The value of the visitor pattern is that it lets you emulate tagged unions in languages that don't have them (e.g., Java 16 and earlier). Of course, Python has no need of this because you can check the type of anything at runtime and the optional type annotations also support union types.
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.
Builder patterns are seriously useful for high complexity state construction with the ability to encore rules to prevent degenerate state.
A good example from my experience might be connecting to a Cassandra cluster it other type of database that can have extremely complex distributed settings and behaviors: timeouts, consistency levels, failure modes, retry behavior, seed connector sets.
Javaland definitely had a problem with overuse of patterns, but the patterns are legitimate tools even outside of Oop.
I haven't done much research into testing frameworks in other languages, but the spock testing framework in groovy/javaland is a serious piece of good software engineering that needs singletons and other "non hard coded/not global" approaches to work well.
Spring gets a ton of hate outside of jabs, and I get it, they tried to subsume every aspect of programming and apis especially web into their framework, but the core spring framework solved complex object graph construction in a very effective way
Oh you hate "objects" but have thousand line struct graph construction code?
It's kind of sad that groovy never took off. It offered all the good parts of java with a ton of good python, ruby, and other langs with the solid foundation of the jvm for high speed execution.
But it's effectively dead. Kind of like Cassandra is effectively dead. The tech treadmill will eventually leave you behind.
Great post. Now let's do C# because if I see a repository pattern doing nothing but calling Entity Framework under the hood again I'm going to rip the planet in half
Singleton is the worst example of design pattern, not sure why these kinds of posts always like to mention it. Singleton is just a hack for avoiding OOP with OOP languages. Obviously python allows non OOP code, so not surprised singleton is useless there.
Great post. I dont write much python these days, but I distinctly remember things being suspiciously easy .. to the point where I started to wonder why arent things so complicated.
I don't agree with the builder pattern. For basic objects yes its a bit silly.
But the ACTUAL value of the builder pattern is when you want to variadically construct an object. Create the base object, then loop or otherwise control flow over other state to optionally add stuff to the base object.
Then, additionally, the final "build" call can run validations over the set of the complete object. This is useful in cases where an intermediate state could be invalid but a subsequent update will update it to a valid state. So you dont want to validate on every update.
I've used the builder pattern in python when I wanted to have the mutable and immutable version of a class be different types. you do a bunch of construction on the mutable version then call "freeze" which uses the final data to construct the "immutable" class.
I remember the fad for dependency injection frameworks in ruby, and the eventual similar pushback pointing out you could just use the language features for most of it
> What happened? Well, it turns out you’re always getting the same instance, no matter what parameters you pass. Your second call to Singleton(name="Bob", age=25) didn’t create anything new — it just silently reused the original object, with its original attributes. No warning. No error. Just quietly wrong.
It's not wrong, it's right. No shit it "didn't create anything new"--it's not supposed to. It's a Singleton.
If I had but one design pattern I would just LOVE to see disappear from Python, it's the need for super(). Don't get me wrong, super() is a clever piece of engineering, but if your code actually needs what it's useful for (C3 linearization, MRO, etc), then you've made things too complicated. I deplore the proliferation of libraries that have embraced the seductive, but ultimately deceptive ways of the mixin, because they saw all the big boys reaching for it. The devil gave multiple inheritance a cooler name, some new outfits, and sunglasses to confuse the Pythonistas and they embraced it with open arms.
Refactor to favor composition over inheritance. But if you really must inherit, single over multiple, and shallow over deep. Eventually your code will less and less need super() and it'll become pointless to use it over the more explicit mechanism, which incidentally makes everything cognitively lighter.
Completely agreed. The codebase I work on really badly abused multiple inheritance all over the place. Some of our classes are 5+ layers of inheritance deep.
All the code I've written since joining has used `typing.Protocol` over ABCs, simple dependency injection (i.e., no DI framework), no inheritance anywhere, and of course extensive type annotations... and our average test coverage has gone from around 6% to around 45%.
It's honestly baffling to see how insanely over-complicated most of the Python is that I see out in the wild, especially when you consider that like 90% of the apps out there are just CRUD apps.
The programmers that insist in using type hints in python usually are the ones that makes these mistakes.
I think the main reason that these patterns do not make sense is because python is a dynamic language.
If you turn off the part of your brain that thinks in types you realize that you can solve most of these in plain functions and dicts.
Using default args as replacement to the builder pattern is just ridiculous. If you want to encode rules for creating data, that screams schema validation, not builder pattern.
On a closer read, TFA shows strong evidence of being AI-generated, at least in parts. Overall it's just super padded and not especially insightful, and it has this quirky writing style that seems... rather familiar. But I especially want to complain about:
> Okay, maybe you want to delay creating the object until it’s actually needed — lazy initialization. Still no need for Singleton patterns.
> Use a simple function with a closure and an internal variable to store the instance:
The given example does not actually defer instantiation, which would be clear to anyone who actually tried testing the code before publishing it (for example, by providing a definition for the class being instantiated and `print`ing a message from its `__init__`) or just understands Python well enough.
But also, using closures in this way actually is an attempt to implement the pattern. It just doesn't work very well, since... well, it trivially allows client code to end up with multiple separate instances. In fact, it actually expects you to create distinct ordinary instances in order to call the "setter" (instead of supplying new "construction" arguments).
So actually it's effectively useless, and will just complicate the client code for no reason.
36 comments
[ 3.3 ms ] story [ 55.1 ms ] threadhttps://www.norvig.com/design-patterns/
Python in practice: there is more ways of doing it than in any other programming language.
Oh Python, how I love and hate you.
But let me dig into just one more layer. Pay attention to the hyphens used to simulate em-dashes, and how they're spaced, versus what you might think of as "obvious" ways to use them. I'm assured that this is a deliberate joke. And of course, now that we have reasonably widespread Unicode support (even in terminals), surely using an actual emdash character is the "obvious" way. Or is it? People still have reasons for clinging to ASCII in places where it suffices. Then consider that this was written in 2004. What was your environment like at that point? How old was Unicode at that point? What other options did you have (and which ones did you have to worry about) for representing non-ASCII characters? (You can say that all those "code pages" and such were all really Unicode encodings, but how long did it take until people actually thought of them that way?) On the other hand, Python had a real `unicode` type since 2.0, released in 2001. But who do you know who used it? On yet another hand, an emdash in a terminal will typically only be one column wide (just as an 'm' character is), and barely visually distinct from U+002D HYPHEN-MINUS. (And hackers will freely identify "dash" with this character, while Unicode recognizes 25 characters as dashes: https://www.compart.com/en/unicode/category/Pd) Reasonable people can disagree on exactly when it should have become sensible to use actual emdashes, or even whether it is now. Or on whether proper typography is valuable here anyway.
> Simple: we just use the language like it was meant to be used.
> Use Default Arguments Like a Normal Human
etc
The question of when a using a given pattern is appropriate is orthogonal to that. The fact that a named pattern has been defined doesn’t imply a recommendation to use it across the board. It depends on the context and on design forces, and those change with time and circumstances. Anti-patterns are patterns as well.
It’s a pity that the idea of design patterns ended up (after the pattern language craze faded) being almost exclusively associated with the specific patterns named and described in the GoF book.
Conflating key/value lookups (dicts) with structured data (classes). They are both useful tools, but are for different purposes. Many python programmers (Myself many years ago included!) misused dicts when they should have been using dataclasses.
Under the hood, classes are also dict lookups, so really, this is mostly about adding type checking (unless you are also using slots).
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.
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.
A good example from my experience might be connecting to a Cassandra cluster it other type of database that can have extremely complex distributed settings and behaviors: timeouts, consistency levels, failure modes, retry behavior, seed connector sets.
Javaland definitely had a problem with overuse of patterns, but the patterns are legitimate tools even outside of Oop.
I haven't done much research into testing frameworks in other languages, but the spock testing framework in groovy/javaland is a serious piece of good software engineering that needs singletons and other "non hard coded/not global" approaches to work well.
Spring gets a ton of hate outside of jabs, and I get it, they tried to subsume every aspect of programming and apis especially web into their framework, but the core spring framework solved complex object graph construction in a very effective way
Oh you hate "objects" but have thousand line struct graph construction code?
It's kind of sad that groovy never took off. It offered all the good parts of java with a ton of good python, ruby, and other langs with the solid foundation of the jvm for high speed execution.
But it's effectively dead. Kind of like Cassandra is effectively dead. The tech treadmill will eventually leave you behind.
But the ACTUAL value of the builder pattern is when you want to variadically construct an object. Create the base object, then loop or otherwise control flow over other state to optionally add stuff to the base object.
Then, additionally, the final "build" call can run validations over the set of the complete object. This is useful in cases where an intermediate state could be invalid but a subsequent update will update it to a valid state. So you dont want to validate on every update.
Classic Design Patterns: Where Are They Now - Brandon Rhodes (https://www.youtube.com/watch?v=pGq7Cr2ekVM)
> What happened? Well, it turns out you’re always getting the same instance, no matter what parameters you pass. Your second call to Singleton(name="Bob", age=25) didn’t create anything new — it just silently reused the original object, with its original attributes. No warning. No error. Just quietly wrong.
It's not wrong, it's right. No shit it "didn't create anything new"--it's not supposed to. It's a Singleton.
Refactor to favor composition over inheritance. But if you really must inherit, single over multiple, and shallow over deep. Eventually your code will less and less need super() and it'll become pointless to use it over the more explicit mechanism, which incidentally makes everything cognitively lighter.
All the code I've written since joining has used `typing.Protocol` over ABCs, simple dependency injection (i.e., no DI framework), no inheritance anywhere, and of course extensive type annotations... and our average test coverage has gone from around 6% to around 45%.
It's honestly baffling to see how insanely over-complicated most of the Python is that I see out in the wild, especially when you consider that like 90% of the apps out there are just CRUD apps.
> Okay, maybe you want to delay creating the object until it’s actually needed — lazy initialization. Still no need for Singleton patterns.
> Use a simple function with a closure and an internal variable to store the instance:
The given example does not actually defer instantiation, which would be clear to anyone who actually tried testing the code before publishing it (for example, by providing a definition for the class being instantiated and `print`ing a message from its `__init__`) or just understands Python well enough.
But also, using closures in this way actually is an attempt to implement the pattern. It just doesn't work very well, since... well, it trivially allows client code to end up with multiple separate instances. In fact, it actually expects you to create distinct ordinary instances in order to call the "setter" (instead of supplying new "construction" arguments).
So actually it's effectively useless, and will just complicate the client code for no reason.