Mostly, it's Java written with Python syntax, specially the gang-of-four patterns. I recall it being even more java-like, so, progress?
The stuff that is actually idiomatic python is either too convoluted or rather obvious.
I know this comes across rather mean, but this is just not a good resource, for either beginners (not very idiomatic) or experienced developers (trivial patterns with trivial examples). There's maybe 2 or 3 good ideas in there, but even those I might be hard pressed to find a real usecase for.
I wholefully agree, the problem with this kind of repos is that Python already makes most patterns trivial. So you either create a trivial example that does not bring value or you introduce unnecessary abstractions for the sake of them.
I am always looking into these repos though because I am still interested in learning new Python tricks.
One of the repos that I found interesting recently is https://github.com/dry-python/returns
All these lambdas seem excessive. "Maybe"-like pattern in Python can look like (None is falsy and custom objects are true):
# note: `None and f()` returns None without calling f
balance = user and user.get_balance()
credit = balance and balance.credit_amount()
discount_program = choose_discount(credit) if credit and credit > 0 else None
Nullable (Optional[Foo] / Union[Foo, None]) and Maybe[Foo] are similar but not quite the same thing. The difference is quite subtle.
Optional[Foo] is same as Foo | None, meaning you can operate on foo with Foo's methods, except that if it's None, you get NoneType errors.
Maybe[Foo] is an actual container. You have to map over or unwrap it to operate on Foo.
The big difference is ergonomics/opinion. You can't actually map over Optional, so every time you wanna use it, you have to manually check if foo is not None. Whereas Maybe, you can operate generically over it. Some folks think "is not None" is better. Personally I hate NoneType errors in prod and find that much more painful than a bit of indirection.
It looks like a distinction without a difference. We can consider `None and f()` pattern as explicit syntax that unwraps objects as necessary without infecting other code.
Background: an object that either None or true in a boolean context (true unless overriden for custom objects).
Given such object, we can consider it in a virtual Maybe container/box.
When we want to use it, we have to unwrap it using `obj and obj.method()` syntax.
Then `obj.method()` is ordinary "unwrapped" call.
Just to remind you. Here's how "ergonomic" Maybe variant from the article look like:
# Type hint here is optional, it only helps the reader here:
discount_program: Maybe['DiscountProgram'] = Maybe.from_optional(
user,
).bind_optional( # This won't be called if `user is None`
lambda real_user: real_user.get_balance(),
).bind_optional( # This won't be called if `real_user.get_balance()` is None
lambda balance: balance.credit_amount(),
).bind_optional( # And so on!
lambda credit: choose_discount(credit) if credit > 0 else None,
)
The dry-python lambda soup is awful, I totally agree there. That's just there mostly for demonstration purposes though. Generally you'd actually use the flow construct (monads) to compose methods.
But `obj and obj.method()` really is not the same thing as `obj.map(method)`. "virtual Maybe container/box" is a nice idea, and does actually type-check with mypy (mostly), but you cannot actually compose it with other functions. The problem is, each time you do `obj and obj.method()`, you end up union-ing type(obj) and type(obj.method).
main.py:40: note: Revealed type is "Union[__main__.User, None]"
main.py:41: note: Revealed type is "Union[__main__.User, None, builtins.float]"
main.py:42: note: Revealed type is "Union[__main__.User, None, builtins.float]"
main.py:43: note: Revealed type is "Union[__main__.User, None, builtins.float, builtins.str]"
True Maybe types are more precise. Maybe if the Mypy engine could be retooled to recognise the `obj and obj.method()` idiom as tantamount to `obj.map(method)`, this could be avoided.
> I recall it being even more java-like, so, progress?
I'd consider Java-like being a regression, not progress.
In my experience, most of the time when people complain about medium-large Python projects being difficult to reason about, it's because they wrote Java code in Python.
In my experience, most Python projects are quite hard to scale or maintain precisely because they weren't written with a proper domain-specific approach. You'll be pressed to find a medium size python project that uses a database and doesn't use the leaky SqlAlchemy/Django ORM abstractions where data and operations on data are "the same thing"; On the same vein, any medium sized project with dozens of, hum, "models" will lead to files with thousands of lines. And don't even get me started on using an HTTP application instance as a container for configuration, usually included in every other piece of logic under the sun.
You can easily guess I'm not a huge python fan (nor java, but it so happens I work daily in medium-sized projects in python), and I do have problems with the "idiomatic/pythonic way", but lets face it - python, while very expressive, is often so convoluted that static analysis of code isn't even possible. If your IDE is not able to quickly determine a given var type, you expect the average developer to be able to do it?
Python is - first and foremost - a prototyping language, and an excellent choice for glue code. For big applications, you will actually learn to enjoy the bitterness and complexity of Java over python, and small stuff like strict typing.
>t this is just not a good resource, for either beginners (not very idiomatic) or experienced developers (trivial patterns with trivial examples)
As someone with experience (just not in Python) I don't mind if the examples are basic, as long as they're idiomatic. I'd prefer a list of very common tasks/problems solved in a pythony way vs some examples of unique and clever uses of python.
Is there a difference between a "clever one-liner that doesnt replicate well" and idiomatic in python? Just because something is idiomatic it doesnt mean its a good approach, nor a good implementation.
> Is there a difference between a "clever one-liner that doesnt replicate well" and idiomatic in python?
100% yes.
Idiomatic would be (to me):
1. things which python does naturally - for instance list expressions rather than manual creation:
managers = [
person for person in get_all_staff()
if person.has_direct_reports()
]
kind of thing, rather than:
managers = []
for person in get_all_staff():
if person.has_direct_reports():
managers.append(person)
So that's an 'idiom'. And a common one.
2. Things that Python makes natural / or less so - by the way it works.
For instance, because of the important whitespace, and no end brackets or `end` keywords or whatever at the end of `if` or function definitions, large nesting inside functions becomes very klunky to follow the logic. So idiomatic would be to use small named functions, and split things into smaller pieces once they become too nested.
> >>> import this
> The Zen of Python, by Tim Peters
>
> Beautiful is better than ugly.
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
> Flat is better than nested.
> Sparse is better than dense.
> Readability counts.
> Special cases aren't special enough to break the rules.
> Although practicality beats purity.
> Errors should never pass silently.
> Unless explicitly silenced.
> In the face of ambiguity, refuse the temptation to guess.
> 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.
> Now is better than never.
> Although never is often better than right now.
> If the implementation is hard to explain, it's a bad idea.
> If the implementation is easy to explain, it may be a good idea.
> Namespaces are one honking great idea -- let's do more of those!
So things like readability beats conciseness, so a huuuuge complex list expression is less pythonic than a manual loop when it becomes too complex.
That is actually a pretty good example of stuff that - in my opinion - hinder readability. I prefer any given day a if <something> assign; instead of assign if <something>. Maybe its because most languages I work/worked with works this way, maybe its because it forces you to read the whole line to actually understand there's an if condition. If I wanted to work with semantic english, I'd use cobol instead.
>For instance, because of the important whitespace, and no end brackets or `end` keywords or whatever at the end of `if` or function definitions, large nesting inside functions becomes very klunky to follow the logic. So idiomatic would be to use small named functions, and split things into smaller pieces once they become too nested.
That's not idiomatic, that is common sense, and common to basically every language. As a rule of thumb, if you have nestings grow deeper than 3-4 levels you're probably better breaking it. This is also a common cyclomatic complexity measure in most static analysis tools. Python makes following the logic clunky even in one liners, because it isn't obvious your code is not branchless. I also understand it is a matter of taste, but I do get quite annoyed with the whole "idiomatic python" thing, because often is just stuff that encourages what - in my humble opinion - are often less-than-ideal practices, that don't translate well to other languages.
> So things like readability beats conciseness, so a huuuuge complex list expression is less pythonic than a manual loop when it becomes too complex.
You don't need to have a huuge complex list. Imagine the example you gave, where later you need to add 2 more conditions to the if clause. What approach you think will age better between changes and refactoring? Might as well do it without list expressions from the get-go, and not having the novice developer who will be maintaining it have to read it 2 or 3 times to understand what is going on.
There's a sentiment that design patterns are ways to make up for missing features in a language. I think that misses the forest for the trees as when people say that they almost always mean the GoF patterns, and not the concept of patterns in general.
Strategy pattern is a great example. First class functions make the GoF pattern a moot point. But it's still useful to understand that this is a useful thing to want to do, whether or not one has direct language support.
I've come to think that that perspective unnecessarily conflates the design pattern with the strategy used to implement it.
Things like the GoF model for how to implement Strategy are kind of a big deal in languages that lack first class functions. But there is also universal value in the basic idea of structuring your code so that you can inject behavior from outside. It's so valuable that many languages weave it into the very fabric of their design. FP fans fixating on the complexity of things like the GoF Strategy pattern and concluding that design patterns are just a band-aid over missing language features strikes me as being like fish noticing the inconvenience of humans needing plumbing and drinking vessels to get their water, and congratulating themselves for not needing water.
This is exactly what I'm saying. It's often true that for the specific GoF implementations they're often a band-aid. But what you said is also true, and much more important.
Better languages change the way we think. First class functions make passing code around so easy and natural there's barely even a name for such a thing. It's just something you do without even thinking. In lesser languages, passing some function around is such a herculean effort people had to ritualize it into an accepted convention with a name and everything.
No matter what pattern we look at, we find it's actually hiding some absurd language weakness. Factories? They're just normal methods that hide the new keyword which introduces hard coupling at the ABI level and can't construct subtypes. Singletons? They exist to work around the fact that classes don't act like objects: classes with static state are natural singletons but they can't implement interfaces or be passed around as objects.
In better languages such as Ruby we discover that new itself is actually a factory method and nobody even realizes it. In Ruby classes are objects and natural singletons and there are no limitations to work around.
The stuff that is actually idiomatic python is either too convoluted or rather obvious.
I know this comes across rather mean, but this is just not a good resource, for either beginners (not very idiomatic) or experienced developers (trivial patterns with trivial examples). There's maybe 2 or 3 good ideas in there, but even those I might be hard pressed to find a real usecase for.