Python Design Patterns (2018)
python-patterns.guide
python-patterns.guide
For eg. reading about prebound methods here: https://python-patterns.guide/python/prebound-methods/. Seasoned python programmers probably wouldn't find anything interesting there, but for me, it was quite useful including instances of where it is being done in python standard library and discussion around when and when shouldn't it be used.
def some_random_function(self)
print(self.internal_state)
...
mymagicobject.func = some_random_func
mymagicobject.func() # -> prints the internal state of the objectBetter yet, improve the programming language, so that any user may benefit, not just those who know about the obscure list.
Hasn't received any updates in the last years but it still holds up quite well, disregarding the Python 2 specific anti-patterns of course which become less and less relevant, as well as the Django-specific ones, which probably are hopelessly outdated.
* Returning more than one variable type from function call: That's fine; the type checker will make sure you don't make a mistake.
* Asking for permission instead of forgiveness: Makes sense for the example given (`unlink`) but it is better to e.g. check types explicitly with `isinstance()` than to catch `TypeError`.
`if known type has expected property`
into
`try prodding this Any object, and catch where it blows up later`
To avoid this, a lot of the new Python code I write is full of dataclasses. I do feel like I'm leaving a little bit of power on the table, but at least I can understand the code at a glance.
You don't strictly need to use dataclasses to accomplish this. Yes, you get stronger guarantees with dataclasses (such as runtime checks, and these come with a runtime cost), but you can declare your members the same and use a properly configured linter that will tell you if you're assigning members from places you shouldn't.
Personally, I tend use dataclasses more for plain old data types. As soon as one starts needing/wanting to control __init__, I find the utility of dataclasses drops precipitously. But, I do still annotate my (public) members with proper types, trying to avoid `Any` as much as possible.
(From my naive understanding, patterns are some language features which should be used over conventionally accepted ways. Please correct if wrong)
Anti-patterns are pretty much the same thing, just where there is some problem with the common solution that everyone is using - perhaps there actually is a language feature that addresses this use case after all, for example.
You only need to validate things once typically. Why force the user to run validation code over and over on a code base unlikely to change?
I'd make a small and mostly pedantic exception to "nothing but re-exports in __init__.py" for cases where you have a module with data (non-Python) files adjacent to the code, but which doesn't need more than one Python file to define its module logic. No point to have a tiny __init__.py that re-exports from an adjacent somemodule.py in that case.
But generally yes, you're right.
This feels like encouraging people to buy a bunch of cameras instead of teaching people how to take good photos.
A good solution may end up looking like some patterns, but sourcing good solutions from a pool of patterns is impossible unless the problem you are trying to solve is the exact same as the one a pattern is solving, in the same environment and context.
I do find the content can be very useful for many, but can we not call them patterns?
Heuristics?
They're mostly useful as training wheels, once you've solved a couple of similar problems you can probably do better using local knowledge / pattern matching against actual experience.
I remember the Bridge pattern helping me along the way, I was trying to design a GUI with multiple back ends in C++.
* Clean Architectures in Python: https://www.thedigitalcatbooks.com/pycabook-introduction/
* Architecture Patterns with Python: https://www.cosmicpython.com/book/preface.html
* Collection of design patterns and idioms: https://github.com/faif/python-patterns)
- - - -
In re: Singleton pattern in Python: one interesting technique is to use a module as a singleton. When client modules "import singlefoo" the first import caches the module object (in the sys.modules dict) and subsequent imports use it: presto! Singleton!
Explanation with a bit more details: https://perl.plover.com/yak/design/
One challenge is having them fresh on my mind and being able to spot when it makes sense to apply them.
Have been thinking about using flashcards and spaced repetition to achieve better retention and ability to apply in practice.
Anyone has experience with this?
Maybe the fact that this kind of work ends up being "spaced repetition" in some sense is part of why it's helpful.
I know and have used these patterns. Some I use frequently, like Observer. The less frequent ones just fade in memory, and I get myself constantly in need to read about many again to refresh and identify which to use in a particular case.
And I suspect I'm missing many potential uses, due to not having them very fresh in mind all the time.
That's where I presume SRS can help...
I get what you’re going for. You learn the patterns, but then you don’t recognize the opportunity to use them. You think you have to keep the pattern in your recent memory so you’ll recognize the opportunity to use it.
Here’s the trick: you don’t have to recognize the opportunity to use a pattern. You just have to recognize the opportunity to use a pattern. Instead of thinking “ah yes, Builder pattern would go here” you can just think “maybe there’s a pattern that would be useful here. I’ll scan my list of patterns taped to my monitor. Builder would maybe work? I’ll double check what that means”.
This is 1. Way easier 2. Consistent across many patterns. Instead of learning to recognize pattern A, B, C and so on, you just get a vague feeling of “pattern”.
This (obviously) doesn’t just apply to patterns. Super useful for web frameworks. List the 10 places you could put behavior (model, field, view, etc) and choose consciously. Same for data structures in algorithms and even all sorts of non-programming stuff.
I covered a few core concepts (e.g., functions as first-class citizens, closures, partial application, etc...) and added a few real world examples of using a functional centric design. The text/format has some rough edges, but overall I think the text is useful for internalizing how to leverage a functional-ish approach.
Other resources.
- Don't use classes for information. Use regular data structures (dict, list, tuple, set) and use functions to manipulate them. If you really want/need something like a struct, use a dataclass.
- The itertools module has a bunch of useful constructs for creating and combining iterators. Take advantage of Python's iterators and construct your own when necessary.
- List comprehensions and generator expressions are great for clear and concise data transformations
- You're going to have to deal with state somewhere in your program. That's fine, some state is necessary to do real work. Don't feel bad about creating classes for things like context managers (e.g. for a database connection) so you can use it in a with statement and it will handle the setup and teardown logic for you. That way your business logic stays concise and Pythonic since those classes can be in separate modules and imported as necessary. I've found this 'functions in the front, classes in the back' approach to writing Python useful as it lets me think about what my program does in a functional manner without denying myself the ergonomics of Python's class-based ecosystem.
One problem is many of these datastructures are easy to mutate. When I need to ensure immutability to a dictionary, for instance - quite often - I can't avoid wrapping it in a class and setting up an interface to lock down mutation.
If Python had a way to declare immutable datastructure, similarly to Clojure, it would make life a lot easier...
> Read-only proxy of a mapping. It provides a dynamic view on the mapping’s entries, which means that when the mapping changes, the view reflects these changes.
https://docs.python.org/3/library/types.html#types.MappingPr...
If function A passes a dict to function B, I would like:
1) Function A to keep the dict intact while function B can manipulate its content;
2) Function A can change the dict after passing to B, and B still keep the original copy, unaware of A's later changes.
One way of doing this is by deep copying dictionaries around, but it can easily become a big performance issue.
I've written a lot of functionalish Python, and I've found that this is a consistent way to breed code that's hard to understand. Instead, I would default to (usually frozen) dataclasses to represent data—they're functional records that also let your code reflect the concepts that make sense for whatever you're doing. A list of dictionaries of strings looks like any other list of dictionaries of strings; a sequence of User objects has clear semantics and, as a benefit, is harder to mutate in unexpected ways.
Use named parameters and bypass the builder pattern.
Store operations in objects/lists. Bypass the strategy patterns with this knowledge. Did you know myobject.mymethod is a function, and you can do x = myobject.mymethod, and then x()? Store methods in lists, pass them as parameters, partially-apply parameters to them.
Generators are more than what the iterator pattern will never be.
And if you do have those problems, it isn't even necessarily what you would reach for first. Though it's at least on the list.
[0] https://twitter.com/ramalhoorg/status/960746929025142784?s=2...
IMO GoF has been obsolete for the last 20 years (50 years if you consider that languages like ML, Lisp and friends already existed then).
Do we have special names for storing strings in data structures?
Do we have special names for storing lists in data structures?
Do we have special names for storing source code as strings in data structures?
Do we have special names for storing abstract syntax trees as data structures?
Do we have special names for storing compiled code as data structures?
Do we have special names for storing pairs of compiled code and related memory in data structures?
Why do we need special names for storing functions in data structures?
When I filter the list of numbers it's not a strategy pattern. Neither is when I filter the string list, or the AST list, or the compiled code+closure list. But, somehow, if it's a function list then it's a strategy pattern.
Free your mind of unnecessary labels.
Yes, we do. For instance, if I store numbers that describe a limit, I'll call it a limit. And it will give a hint how it is used. Do I call all numbers in a data structure limit? Of course not - the same is true for functions in datastructures.
> Why do we need special names for storing functions in data structures?
Well, it is helpful. There are many reasons to store functions in datastructures. For example to do mappings, to cache/memoize them or even to create interpretable datastructures, using free monads.
But the strategy pattern describes the situation where there are multiple functions and they all serve the same purpose and have the same interface (and thus are interchangeble) and usually one is chosen and applied - but it is expected to be different ones, depending on the context.
Do we really need a name for that or use it in code? I don't know. But that's not the my point - which is that the concept doesn't go away just because it is easier to use it.
> When I filter the list of numbers it's not a strategy pattern. Neither is when I filter the string list, or the AST list, or the compiled code+closure list.
Sure, if you don't change the way you filter then it's not a strategy pattern.
> But, somehow, if it's a function list then it's a strategy pattern.
Now you have lost me. Maybe we disagree because we are talking about a different thing?. A list of functions does not make a strategy pattern. And very often, strategies are implemented using inheritance (or composition) without any lists being involved. At least that's how I remeber the pattern when I saw it (in the pure functional programming world we indeed rarely call it like that) and how it's written in the GoF book.
Why it is so different to having a bunch of numbers in a list and choosing one of these numbers via some criteria, then doing one of the allowed operations for that number?
Bear in mind with the function I'm doing exactly the same: do one of the allowed operations for it (invoke the function).
Well, you can argue that there are also different numbers and you can do devision only by some of them. But in fact, if you were to separate them and wrap them to do things differently, then this would get closer to the strategy pattern.
No. The implementation of some DPs is trivial in some languages, but the DPs don't disappear. The idea of DPs primarily as boilerplate implementation recipes rather than solution concepts with rationales comes from the limitations of Java, etc.,. not the DPs themselves.
I see people claim that GoF patterns are obsolete, and that builder is one of the ones that are old hat, but for making configurations readable it can be the best solution.
t = TemplateBuilder() .withFoo(foo) .withBar(bar) .excludeBaz() .build()
over:
t = Template(foo=foo, bar=bar, baz=none)
That is just horrible...
> maybe parts of the construction might be conditional
No builder pattern needed for that, just use optional arguments and/or an option/nullable type.
ipv4 = IPAddress().v4("127.0.0.1").build()
ipv6 = IPAddress().v6("::1").build()
Trying to perform something like this in Python with merely a constructor on an IPAddress type would lead to a not-so-obvious interface (such as use X set of named args to construct a v4 address or use Y set of named args to construct a v6 address, but using X & Y in the same constructor is a runtime error). This is my contrived example for where the builder pattern might make sense.With your example, I don't see any compelling advantage to use the builder pattern. In fact, I'd argue as-is, the builder is unnecessary, is more verbose, adds additional complexity, and you should just use the constructor with named args instead.
The point behind the builder pattern is to perform nontrivial initialization that is hard/difficult/impossible to perform in a normal constructor a la C++, C#, Java, etc. If you're not performing nontrivial initialization, you've just introduced extra verbosity and complexity for no gain, so don't use the pattern then. This is, of course, just my opinion.
For example, if you see code that has a lot of overloaded constructors followed by an `init` function that must be called after construction, or code with a lot of overloaded `init`-type functions, only one of which shall be called; the builder pattern might make sense there.
ipv4 = IPAddress.v4("127.0.0.1")
ipv6 = IPAddress.v6("::1")
KISS.I'll post an example in a bit of what I mean, but using a builder lets me use functions to break down the template into readable pieces.
And I'm not saying that it is impossible, I'm talking about readability.
After years of Python development, I encountered all the same problems and reached all the same conclusions. I wish I read this at the beginning of my career.
The 'enterprise python' battle begins. Good luck.
My brain puts it next to: globe of China, Jewish Olympic games (real thing).