Readability counts.
[...]
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.
—excerpts from The Zen of Python
This part of Python’s culture was a reaction to Perl’s motto, “There’s more than one way to do it”. Perl has been derided as a Write-only language, and despite its initial popularity, has been far eclipsed by Python. We can speculate about the various reasons, but I think this is one of them. Other ecosystems have their own cultures which emphasize different tradeoffs. Ruby, for example, seems to have inherited Perl’s take.
The Zen is full of allusions and apparent contradictions. It’s not meant to be obeyed as much as meditated upon; less about answers, and more about which questions to ask in the first place. So take my commentary as interpretation, not law.
Among other things, this part of The Zen is pointing out that code is read more often than it is written. It’s more important to make code easy to read than easy to write. It’s possible to write bad code in any language, but “bad” is partly a cultural interpretation. (And partly not.)
In Python, in the interest of readability, one should first do things in the most obvious way, until one has very good reason to do otherwise.
But “obvious” is also cultural. What is idiomatic in C is not, in Python, idiomatic (or “pythonic”), the “Dutch” part alluding to Guido van Rossum, the creator of Python, who made a lot of judgement calls about how Python was going to be, and what counts as “normal”.
So “obvious” here means “obvious to acculturated Python programmers”. It’s a warning about being “too clever”, and thereby being obtuse. But clever is OK when it’s easy to read! Are you trying to show off or are you trying to write good code? If you are an acculturated Python programmer, then you can judge what’s obvious and isn’t. But culture isn’t a static thing. If you’re a part of it, and you know better, you can push boundaries a little.
(To read all of The Zen, import this.)
My exercises for konstell are not necessarily pythonic. They illustrate particular points I was trying to teach; weaknesses I noticed in her understanding at the time. That you can do something doesn’t mean you should.
So, like anything else in Python, my main heuristic would be, does your decorator make the code easier or harder to read, for an acculturated Python programmer? Does it have benefits worth the costs? Decorators are a Python feature, and they’re used a lot. Excessive length is bad for readability too, and often neglected. Being more concise can be worth being slightly less obvious. Does it make tooling easier or harder to use? Does it make testing more or less difficult?
The multi-line lambda one always makes me want to facepalm. I hear it a lot, but this one is so confused that it’s not even wrong.
First, lambdas in Python can have as many lines as you want. That makes the premise invalid. Seriously. My Hissp project is a Lisp compiler that targets a subset of Python. Its lambdas have the implicit PROGN typical of Lisp, and they compile to Python lambdas just fine. You could wrap your entire Hissp module in a progn and it will compile to a lambda expression that goes on for pages.
So why are people confused about this? I think they’re conflating “lines of code” with “statements”, which are not at all the same thing. It’s true that certain kinds of Python statements typically fit on one line, but none of them have to, and many kinds (block statements, e.g. try-except/try-finally) typically don’t.
So let’s try to steelman this: even multi-line lambdas in Python can’t directly contain statements. (Or, from a certain point of view, they contain only one: an implicit return. But they can call exec() on a string that does, or call other things that do.)
Second, true functional languages don’t have statements to begin with, only expressions. (Or, from a certain point of view, they only have “expression statements”.) Statements are a holdover from assembly, when the original Fortran mixed math expressions with machine code instructions (the “statements”).
When programming in the functional style, which is when you want lambdas, you don’t use statements anyway. Expressions are all you need! You don’t even need a progn unless you have side effects, which is also not functional style.
So then the argument becomes “Python would not need decorators if anonymous functions could have statements.”
Now what does the “need” part mean? Decorators are just syntactic sugar. You can get exactly the same behavior without them, so what use are decorators at all? Let’s look at what the sugar does:
“Need” might be overstated. But why is the decorator better? The main reason is that it eliminates the duplication of <name>, which appears three times in the desugared version. It’s also shorter and allows you to write the decorator expression before the function. Do lambdas have <name>s? No.
So what are they suggesting we could do instead? It’s probably
which does already work in Python if <body> doesn’t have statements (and it wouldn’t in the functional style). But we’re still missing the function’s docstring, and its __name__ attribute will be '(lambda)' instead of <name>. Hypothetically, to fix these, it then becomes something like,
This doesn’t work because def isn’t an expression in Python. It’s not about lambdas anymore.
Now the steelman argument has become “Python would not want decorators if function definitions were expressions.”
But we can see that the decorator version is still better. It doesn’t duplicate the <name>. It doesn’t need another layer of indentation, which gets even worse when nesting these. Can we fix this? Maybe?
def defn(*decorators):
def _(name, f):
while decorators:
f = decorators.pop()(f)
globals()[name] = f
f.__name__ = name # etc.
return _
defn(<decorator expression>)(
"<name>", lambda <args>:
<body>
)
This only assigns to the top level because it uses globals, even if it’s nested in a class or function. Perhaps if we had some kind of preprocessor macro that expanded to an assignment? But now we’ve just re-implemented decorators.
Feedback
In the “Preprint” example, you use
@wraps
without explaining it. I think it’s worth noting it is imported fromfunctools
and it’s general purpose?The section “Branching without if” is a bit confusing, because it is unclear if those examples work or need more code.
Discussion
This might be out of scope of the mentorship, but I’d like gilch’s opinion/heuristics on:
Good decorator use vs. abuse
The argument “Python would not need decorators if it supported multi-line anonymous functions”
Re #1.
This part of Python’s culture was a reaction to Perl’s motto, “There’s more than one way to do it”. Perl has been derided as a Write-only language, and despite its initial popularity, has been far eclipsed by Python. We can speculate about the various reasons, but I think this is one of them. Other ecosystems have their own cultures which emphasize different tradeoffs. Ruby, for example, seems to have inherited Perl’s take.
The Zen is full of allusions and apparent contradictions. It’s not meant to be obeyed as much as meditated upon; less about answers, and more about which questions to ask in the first place. So take my commentary as interpretation, not law.
Among other things, this part of The Zen is pointing out that code is read more often than it is written. It’s more important to make code easy to read than easy to write. It’s possible to write bad code in any language, but “bad” is partly a cultural interpretation. (And partly not.)
In Python, in the interest of readability, one should first do things in the most obvious way, until one has very good reason to do otherwise.
But “obvious” is also cultural. What is idiomatic in C is not, in Python, idiomatic (or “pythonic”), the “Dutch” part alluding to Guido van Rossum, the creator of Python, who made a lot of judgement calls about how Python was going to be, and what counts as “normal”.
So “obvious” here means “obvious to acculturated Python programmers”. It’s a warning about being “too clever”, and thereby being obtuse. But clever is OK when it’s easy to read! Are you trying to show off or are you trying to write good code? If you are an acculturated Python programmer, then you can judge what’s obvious and isn’t. But culture isn’t a static thing. If you’re a part of it, and you know better, you can push boundaries a little.
(To read all of The Zen,
import this
.)My exercises for konstell are not necessarily pythonic. They illustrate particular points I was trying to teach; weaknesses I noticed in her understanding at the time. That you can do something doesn’t mean you should.
So, like anything else in Python, my main heuristic would be, does your decorator make the code easier or harder to read, for an acculturated Python programmer? Does it have benefits worth the costs? Decorators are a Python feature, and they’re used a lot. Excessive length is bad for readability too, and often neglected. Being more concise can be worth being slightly less obvious. Does it make tooling easier or harder to use? Does it make testing more or less difficult?
Re #2.
The multi-line lambda one always makes me want to facepalm. I hear it a lot, but this one is so confused that it’s not even wrong.
First, lambdas in Python can have as many lines as you want. That makes the premise invalid. Seriously. My Hissp project is a Lisp compiler that targets a subset of Python. Its lambdas have the implicit PROGN typical of Lisp, and they compile to Python lambdas just fine. You could wrap your entire Hissp module in a
progn
and it will compile to a lambda expression that goes on for pages.So why are people confused about this? I think they’re conflating “lines of code” with “statements”, which are not at all the same thing. It’s true that certain kinds of Python statements typically fit on one line, but none of them have to, and many kinds (block statements, e.g. try-except/try-finally) typically don’t.
So let’s try to steelman this: even multi-line lambdas in Python can’t directly contain statements. (Or, from a certain point of view, they contain only one: an implicit
return
. But they can call exec() on a string that does, or call other things that do.)Second, true functional languages don’t have statements to begin with, only expressions. (Or, from a certain point of view, they only have “expression statements”.) Statements are a holdover from assembly, when the original Fortran mixed math expressions with machine code instructions (the “statements”).
When programming in the functional style, which is when you want lambdas, you don’t use statements anyway. Expressions are all you need! You don’t even need a progn unless you have side effects, which is also not functional style.
So then the argument becomes “Python would not need decorators if anonymous functions could have statements.”
Now what does the “need” part mean? Decorators are just syntactic sugar. You can get exactly the same behavior without them, so what use are decorators at all? Let’s look at what the sugar does:
becomes
“Need” might be overstated. But why is the decorator better? The main reason is that it eliminates the duplication of <name>, which appears three times in the desugared version. It’s also shorter and allows you to write the decorator expression before the function. Do lambdas have <name>s? No.
So what are they suggesting we could do instead? It’s probably
which does already work in Python if <body> doesn’t have statements (and it wouldn’t in the functional style). But we’re still missing the function’s docstring, and its
__name__
attribute will be'(lambda)'
instead of <name>. Hypothetically, to fix these, it then becomes something like,This doesn’t work because
def
isn’t an expression in Python. It’s not about lambdas anymore.Now the steelman argument has become “Python would not want decorators if function definitions were expressions.”
But we can see that the decorator version is still better. It doesn’t duplicate the <name>. It doesn’t need another layer of indentation, which gets even worse when nesting these. Can we fix this? Maybe?
This only assigns to the top level because it uses globals, even if it’s nested in a class or function. Perhaps if we had some kind of preprocessor macro that expanded to an assignment? But now we’ve just re-implemented decorators.
The relevant context is the earlier definition of
@if_
.So
would have the same behavior, and does not itself have an
if
statement. I’ve implementedif
without usingif
.Ah, I missed a section on
@wraps
. Added it here.Also renamed “Branching without if” to “Making Branching Statements without Using if”
Also added some command line outputs for a couple examples at the end.