Live data from Hacker News

Design patterns you should unlearn in Python

lihil.cc

81–90 of 110 posts

Re: Design patterns you should unlearn in Python

#81
post #77

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.…

Python type hints are hugely valuable both as a means of correctness checking, but also just as a means of documentation. It strikes me as incredibly shortsighted to say you can forget about types just because it’s a dynamic language. The types are absolutely still there and need thought about. They just aren’t defined or used in terms of allocation and management of memory.

Usually with OOP several builders are composed together to express the creation of some data. These builders have functions with types, which define the rules for the creation of the objects.

My point is that the CarBuilder is not a real type that relates to the business, but something that we had to create to encode some behaviour/rules.

Some function that validates that a dict is a valid car is much more explicit that lots of different builder classes in my opinion.

Re: Design patterns you should unlearn in Python

#82
post #69

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…

> 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

That book is the closest we came to establishing a common language. I remember brushing up on the names of design patterns whenever I had an interview. Ultimately, though, it didn't yield any benefit that the industry is missing now.

Like you said, the fundamental idea behind the book was that consciously naming, cataloging, and studying design patterns would improve communication among programmers. There was also an idea that studying design patterns would give beginning programmers a richer repertoire of programming techniques faster than if they had to figure them out themselves.

Looking back with decades of hindsight, my belief is that awareness and intentional use of design patterns made no difference whatsoever. Some names stuck, and would have anyway. Others didn't, and years of status as official "design patterns" in a book widely studied across the industry couldn't make them. The younger programmers I work with who had no exposure to the GoF book, and for whom "design patterns" is something that dusty old farts used to talk about, use patterns like Flyweight, Proxy, Command, Facade, Strategy, Chain of Responsibility, Decorator, etc. without knowing or needing a name for them, and they communicate amongst themselves just as efficiently as my generation did at the height of the design pattern craze.

In the final analysis, I have never looked at the less experienced programmers around me and thought, "This situation would go faster and smoother if they had studied design patterns." The generation that learned to program after design patterns had faded as an idea learned just as quickly and communicates just as well as the generation that studied them assiduously as junior programmers like I did.

Re: Design patterns you should unlearn in Python

#83
post #82
post #69

Earlier quoted context omitted.

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…

> 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 That book is the closest we came to establishing a common language. I remember brushing up on the names of design patterns whenever I had an interview. Ultimately, though, it didn't yield any benefit that the industry is mis…

> Like you said, the fundamental idea behind the book was that consciously naming, cataloging, and studying design patterns would improve communication among programmers.

> The younger programmers I work with who had no exposure to the GoF book.....and they communicate amongst themselves just as efficiently as my generation

> The generation that learned to program after design patterns had faded as an idea learned just as quickly and communicates just as well as the generation that studied them assiduously as junior programmers like I did.

I've never read GOF so I don't know if they emphasize communication, but I have read and studied many other programming pattern books and communication is low on the list of reasons to learn them in my opinion. Their only purpose for me is to organize code in a way that has been proven to "scale" along with a codebase so that you don't end up with a plate of spaghetti.

Re: Design patterns you should unlearn in Python

#84
post #74

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…

Isn't super() also commonly used in languages that have only single inheritance?

Re: Design patterns you should unlearn in Python

#85
post #16

He didn't mention the worst pattern, the visitor pattern, which has extremely few use cases.

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.

Re: Design patterns you should unlearn in Python

#86
post #6

The Zen of Python: there should be one obvious way to do things. Python in practice: there is more ways of doing it than in any other programming language. Oh Python, how I love and hate you.

The actual text:

  $ 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.

Re: Design patterns you should unlearn in Python

#87
post #66

I've never seen anybody do that... In Python you can use a module as a singleton (mentioned in the article). Or provide some data like: from functools import lru_cache class Whatever: pass @lru_cache(maxsize=1) def get_whatever(): return Whatever() And use `get_whatever` as your interface to get the resource.

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...

Another technique I've seen is to hide or overwrite the name of the class, such that client code only knows about the instance (and is expected to use it directly rather than doing any kind of access or instantiation). Of course, this doesn't give you lazy initialization unless you lazily import the module, at which point you would definitely be better off just using the module object directly.

There's also code out there which replaces the cached module object with a class instance in top-level code! This is especially used to work around the prior lack of module-level `__getattr__` (and `__dir__`), added in 3.7 (https://peps.python.org/pep-0562/). But you might still need it if for some reason you want to hook into the lower-level `__getattribute__`. And Andrew Moffat's `sh` package still does this (https://github.com/amoffat/sh/blob/develop/sh.py#L3635) even though it now only declares support for 3.8 and above. (Perhaps there was simply no clear reason to change it.)

Re: Design patterns you should unlearn in Python

#88
post #24

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 s…

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.)

Re: Design patterns you should unlearn in Python

#89
post #74

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…

Isn't super() also commonly used in languages that have only single inheritance?

If the language is limited to only single and shallow inheritance, then super() becomes a syntactic convenience that saves everyone the burden of spelling out the inheriting class. But in Python, even if your code emulates these constraints, you lose in clarity from using super() because someone reading your source has to wonder if or why it was specifically needed, since its main purpose is to resolve the kinds of conflicts that arise in complex inheritance scenarios (diamond, problematic cycles, and such). So, to need it is to make your code complicated. To not need it while using it, is to lose in clarity.

Re: Design patterns you should unlearn in Python

#90
post #67

Earlier quoted context omitted.

I'm tired and I'm not sure I understood your question correctly, sorry if it doesn't address your point: In python test library, you have something called 'monkeypatch' that allows you to intercept calls to specific functions or classes and set the response yourself (I mostly use it to mock API responses tbh but it can do a lot more, an really complex operations). Monkeypatch only operate in the scope of the function…

The answer if I understand correctly is if you want to use testing frameworks in Python, you should probably not be using global variables and you should probably actually be using the Singleton pattern.

Python test frameworks generally do mocking by monkey-patching, and they take care of it for you. This does not generally require global state, either in the form of module-level variables or singletons. It works by doing things like replacing the attributes of an existing class or module object. (Python takes "everything is an object" seriously, so a module is an object whose attributes are the functions, classes and other global variables of the corresponding file's code, roughly speaking. It is not simply reflected as such through a reflection API, like in Java; it is directly represented with an object. This is the sort of benefit dynamic typing gets you, along with the object-qua-dictionary-of-attributes model.)
Post reply on HN