> I agree, but to fair to the author I do not think the vast majority of people are implementing thread pools in python.
This trope needs to die. While loops are a basic language feature. A thread pool is one very specific example of where that language feature is necessary. Every very specific example is trivially dismissed with "the vast majority of of people aren't doing that very specific thing." It's the laziest rebuttal, and not even wrong.
Cool things I've seen written in python that need while loops: http servers, games, more generally anything that listens on a port or depends on user input, mathematical code that depends on computing a base-k representation, stack-based algorithms...
Hold on, let's stop there. Python sucks at recursion. So bad it's capped at a very small depth. The only reasonable workaround is to avoid the call stack and roll your own, which involves... you guessed it, a while loop. So, this linter prefers buggy stack-smashy code. That's nice. Adults can use another linter.
Not when it's at the cost of readability. The example "better" code fails my readability test horribly. I'd gladly take C's ternary operator over this monstrosity:
When I first started using python, I assumed it had a normal (c-like) ternary operator and was a little surprised to discover the syntax we're talking about. But after some time, it actually feels very natural to me, and is arguably more intuitive than ?: syntax. I assume this is where the author is coming from.
No, that's not good. If condition() is true, x shouldn't take the value 5, even temporarily. The combination of
(x = 5, condition())
is inconsistent state (using the word "consistent" in the same sense as the C in ACID).
It really is clearest and safest to avoid inconsistent state, even if is only transient. There are so many ways programs can be wrong; no need to deliberately create inconsistent state when there's no need to.
In your version, we need to check the definition of 'condition', to see whether the 'x = 5' statement has any affects on its execution. The ternary won't assign the variable until we know its value.
Also, your version isn't safe to use with objects (e.g. 'myObject.x = ...'), since the initial assignment could trigger arbitrary code (properties, __setattr__, etc.).
Also, your version isn't safe to use when the right-hand-side has effects, e.g.
x = fetch_config_url() if remote else read_config_file()
My favorite way to write that is with expression oriented langauges. Example in OCaml:
x = if condition() then 5 else 4
I dislike how the regular order of if is changed when used as an expression. I don't know how you could retrofit that into existing Python tough. Probably a consequence of defining blocks with whitespaces.
Yeah, Python's willingness to break up conceptual concepts into separate parts of an expression such that you might need to backtrack while reading has always seemed an odd choice to me, exemplified by list comprehensions, especially when nested.
But when you write "condition ? first : second" or "(if condition first second)", the first common value is still given before the second uncommon value, so what's the advantage of putting the condition BETWEEN first and second, instead of BEFORE? After all, the condition has to be EVALUATED first before deciding which one to use.
Infix "if" conditions just scramble up the order of evaluation from the order of program source code.
The argument is that the most common value should be first and the rest can be thought of as rare and almost ignored. I don’t always agree, but still prefer it to C syntax which I have to look up every time.
To add to the cacaphony of counterarguments to this, this can introduce nonobvious bugs when refactoring.
If the condition-guarded statement is nontrivial and you remove the assignment, everything stays valid. If otoh you use a ternary or
if x:
y = 1
else:
y = 2
If you now delete the else block or something (like the assignment from a larger else block), a linter can warn you that y is undefined. Otherwise you'd need a test to ensure correctness.
I don’t think this criticism is valid for Python specifically where the most Pythonic expressions, generators of all forms, are read front to back like this.
[ x.attr for x in list if x in someset]
There are languages where this kind of thing would be considered ugly and you’re supposed to use map/filter but in Python they’re the best practice. Every Python programmer is already trained to read expressions like this.
Sorry for the php pseudo code, I'm on mobile. And this is a very friendly example of what I'm trying to get across. It's self documenting code and worth the extra bytes.
I've seen way too many insane conditionals to agree that less code is better.
Yeah I don't like them as implemented, because most of my 'if' statements are in response to unforeseen things, so my mental expansion of it is 'Now, stop and check if...'
I wouldn't mind it so much if I could write it as:
I used to get flack for using not just if expressions as ternaries, but also for using unless. Then I started teaching PERL at my company and drilled it into all the fresh new minds.
Also worth remembering that a perl statement can be converted into an expression with a do block, so you can write
my $x = do {
if (foo()) {
4
} else {
5
}
};
(and yes, I know, that example would look fine as a ternary - but this is meant to illustrate the syntax possibility, not where I'd specifically use it - and once the logic within one of the two conditional branches gets more complicated, switching to do+if+else can make for clearer code)
I had a good chuckle as while reading this comment I was thinking "oh, I saw code like that just last week while reading SQL::Abstract's source", and then I saw the username. :)
Sometimes the logic in at least one of the arms of the if/else is sufficiently complicated to make it worthwhile (e.g. cases where you find yourself needing a 'do' block inside a ternary).
Also doing that with if/elsif/elsif/else is often far more readable than nested/chained ternaries.
Both read grammatically fine to me. If you prefer to do things in 4 lines rather than one, and repeat the variable, that's up to you I guess - a lot of people seem to agree - but honestly I don't get what the fuss is about.
Agreed - relatedly, I really dislike one-liners in code because more often than not, it's done for the sake of being "clever" rather than having readable code for the next person to quickly skim and understand.
I get that it's a "flex" or some sort, but honestly in production code my experience is that it reduces productivity. Programmers need a little more squinting to truly understand what that piece of code is doing.
I find it similar to run-on sentences in books - we don't like that, and in Business Writing courses they explicitly say to not do that. Code should be similarly readable.
So...you should do more per line, since code is all one-liners, its just a choice of how many, so if you don't like them, you should reduce the number?
Honestly, the Python version is far more readable to me than the C, and that's even with close to a decade with C before encountering Python or any other language using that style.
Though Ruby’s unless modifier is often slightly better for readability.
In C, I always put the ternary clauses on their own lines prefixed with the operator. This always makes things readable.
int result = condition
? value * 12
: something_else();
and in the case where the condition is sufficiently complex:
int result =
(
some_condition()
&& another_condition()
&& yet_another_condition()
)
? value * 12
: something_else();
For me, at least, this is entirely readable. The unfortunate bit is that there is no formatter in existence (yet) that can handle this for C, or really any other language with similar syntax.
Python's "Black" formatter actually does the best job here, yet the python ternary syntax is still very verbose and strange IMO.
> The unfortunate bit is that there is no formatter in existence (yet) that can handle this for C, or really any other language with similar syntax.
Prettier does it fine for JS, which uses C-style ternary syntax.
> Python's "Black" formatter actually does the best job here, yet the python ternary syntax is still very verbose and strange IMO.
To me, its quite natural when used sensibly, since if you drop everything after the if it is the normal-case value. Though I would slightly prefer if the ternary form was:
<default-valur> unless <alternative-condition> then <alternative-value>
instead of:
<default-value> if <default-condition> else <alternative-value>
Prettier does okay, it has some weird edge cases though that make certain code entirely unreadable. But they're rare. I still prefer how Black does things, e.g. splitting complex expressions into multiple lines using parenthesis.
> Though I would slightly prefer if the ternary form was:
Tools that enforce "expert level" coding standards do make code more concise, but also less comprehendible for the less experienced developers. I never saw the harm in four lines of code instead of one, especially when it's much more readable.
I am not a fan of the examples in the article, but for tools like rubocop in ruby, I am pretty glad it sometimes uses semi-obscure but more "correct" forms.
When I started learning ruby a bunch of the changes flew over my head, but it is also an excellent entry point to look further into best practices, and concrete examples of how my code could be better.
The condition is of course the tool being itself extremely good, otherwise it's just everyday hell (looking at you, phpstan...)
Agreed, but I work with a lot of sysadmins who will never master Python. They'll use it, but never be at the level of a software developer. And that's OK because they have tons of other skills which are super valuable. I'd rather have our monitor scripts and glue code be more readable than complex.
I would never demand this for someone writing a Python app or module that out team would use as a normal import, just the stuff we actively manage.
I agree with the general concept of "Tools that enforce "expert level" coding standards do make code more concise, but also less comprehendible for the less experienced developers." but this does not seem applicable here - it's not "expert level" coding, the suggested method is obviously readable to anyone who writes Python and is something that a programmer who has never used Python would learn in their first day of learning Python, it's not an advanced concept.
Probably the whole concept of idiomatic Python is that there shouldn't be different styles of Python to which you graduate according to skill or experience, but rather that the simple, readable way of writing things should be done by everyone, always, from novices to experts. Code-golfing with dense weirdness would be "anti-Pythonic" but this ternary expression would be readable even for if someone who's never seen Python would read it as pseudocode in some theoretical CS paper.
in general i agree, but i'm not sure what you're referring to as "expert level" here - if it's ternary operations, those a pretty basic concept, and i don't think there's a clear argument to be made that they're more or less readable than expanding out the if statements, it mostly just comes down to preference.
Sure, once you cherry-pick the most trivial imaginable example ternary expressions are easy enough to read. First counter-example which came to mind:
print("yes") if random.choice([True, False]) else print("no")
Does this do the right thing? I was pleasantly surprised to find that this is indeed lazily evaluated, but that's not at all intuitive: first because `print("yes")` comes before the conditional (note that the Lisp example had the conditional first), and second because not all popular languages work like that.
There are lots of examples where ternary makes things obviously harder to read, such as when at least two of the three expressions are non-trivial. Which one would you rather read?
Neither of your examples qualify for the lint rule that's about cases that assign to the same variable, so I'm not sure why you accuse the author of "cherry-picking"?
Hating linters is like hating a weight scale for telling you your weight, or hating a blood exam for telling you that you have high cholesterol.
If the linter says your function is too long then you refactor it, you don't turn off the linter. You also fix it immediately, not "later" (broken windows theory) before the next offender copies and pastes the offending code or continues making it worse.
In underdeveloped countries, governments manipulate the definition of unemploment to make themselves look better. Does that solve anything? no. It actually makes things worse because now you cannot mobilize people and resources to solve a problem you don't have. It's wrong. And so is turning off linting.
Linters tell you when your code doesn't respect certain rules. That's it. These rules may or may not lead to better programming, that depends on the rules themselves. I could build a linter that forbids all variable names longer than three characters. Would that make the code good? I doubt it. Sometimes the extra weight you notice on the scale is just muscle, and there's nothing to worry about.
That's not an argument but an example. You associated linters and scales saying that people hate them because they tell the truth. My argument is that the truth that these tools express is limited. A basic scale can only tell you if your weight went up, down or stayed the same. It's a useful information but it lacks context and can be misinterpreted if taken alone. Same thing with a linter. While the linter is supposed to encode best practices, code doesn't exist in a vacuum and had a context. For every linting rule, there is probably a certain number of good reasons to break them.
> Anyway, this is just a style thing. It doesn’t affect program correctness or structure in a meaningful way.
The nice thing about ternaries is that they're expressions, so they don't "infect" our code like statements (e.g. 'if'). For example:
if xCond:
x = x1
else:
x = x2
if yCond:
y = y1
else:
y = y2
if zCond:
z = z1
else:
z = z2
foo(x, y, z)
This lint rule will tell us to do the following instead:
x = x1 if xCond else x2
y = y1 if yCond else y2
z = z1 if zCond else z2
foo(x, y, z)
Now that our branching is done with expressions rather than statements, we can actually in-line them, which seems much nicer (less chance to get them mixed up; no need to invent new names; no side-effects, e.g. accidentally overwriting an existing variable; etc.)
foo(
x1 if xCond else x2,
y1 if yCond else y2,
z1 if zCond else z2,
)
If we really want the names, we could define them using the 'walrus operator':
foo(
x := (x1 if xCond else x2),
y := (y1 if yCond else y2),
z := (z1 if zCond else z2),
)
Personally I would use separate 'x = ...' statements rather than :=, since we want to explicitly perform effects (binding names).
Except it's different semantics. The example with 'foo' and the walrus operator doesn't even use keyword arguments, and the keyword arguments may not even be valid, depending on the definition of 'foo'.
> All the walrus gets you is making the x "infect" the surrounding namespace which is surprising.
Yes, that's what I intended. I was pointing out that the foo(x1 if ...) version does not behave the same as the separate 'x = ...' statements, since it doesn't bind the x/y/z variables.
This is usually an advantage (if we don't need those values for anything else), but for completeness I noted that we could use the walrus to recreate the exact behaviour (keyword arguments can't do it, since they only bind variables inside the function call, which (usually) has no observable difference to using positional arguments). You're right that the walrus's effect on the surrounding namespace is 'surprising', and that's why I would prefer to use separate statements if we really want those names defined (since that's less surprising).
I think I might be in the minority here that I prefer the fully laid out statements.
Sure in small cases this small ternary use is great! However too many times I've seen people chain them for far too many characters just to be "on one line".
We don't use one letter variable names anymore, so in the same reasoning why should we do the same to our code?
Yup; the booleans are difficult enough from a logic point of view, don't need to make it more complicated by using syntactic sugar.
I strongly disagree with the author's idea that shorter is better. Clarity trumps conciseness. I'll admit that not enough conciseness can impact clarity, but there's ways and means to clean that up that don't involve clever code.
I hypothesise that the actual meaning (possibly subconscious) underlying those uses of "complicated", "syntactic sugar", and "clever code" is 'not the way I'd write it'; and that "clarity" and "clean" actually mean 'the way I would write it'.
To test whether those statements are guiding principles, or merely ad-hoc justifications, I propose the following pylint rules based on those principles:
no-ternary-sugar:
Replace 'x if y else z' with '{True: lambda: x, False: lambda: z}[y]()'.
This is less concise, but improves clarity by using booleans explicitly,
and making the delayed evaluation of the results clear; both of which are
implicit in the 'if/else' syntactic sugar.
no-elif-sugar:
Replace:
if foo:
bar
elif baz:
quux
...
else:
foobar
With:
if foo:
bar
else:
if baz:
quux
else:
...
else:
foobar
This is less concise, but makes the branching structure clear, unlike the misleading
"flat" appearance of the 'elif' syntactic sugar.
no-special-case-patterns:
Replace:
if foo:
bar
else:
baz
With:
match foo:
case True:
bar
case False:
baz
'if/else' is syntactic sugar for pattern-matching a boolean, which is left implicit.
Explicit matching improves consistency with other use-cases, and makes the relationship
between branches and boolean clear, at the expense of conciseness.
(Also applies to single-armed 'if', which will only have a 'case True:').
Of course, the combination of no-elif-sugar and no-special-case-patterns would give extreme clarity like this:
match foo:
case True:
bar
case False:
match baz:
case True:
quux
case False:
...
case False:
foobar
I personally would have wrote the first one as a first pass, mostly so I could probably at some point put break points on what is going on. Then probably called it 'done'. I had actually forgot you could even do this thing in python. The highest complement I get from other programmers is 'your code is easy to read'.
Everyone has their own 'style' they like. It is usually not that big of a deal. It becomes a big deal if you get someone on the team who becomes obsessed with it, or someone who is always sloppy. Compact styles tend to be harder to decipher than simple ones. As when you are reading them they usually are not the same style context as all the other code around it. So it causes your brain to have to stop and figure it out. With python sometimes those compact styles actually run faster, so they can be handy to know about (test it though).
In java there is this thing where many will do things like blah = x().y().z(); Yet at any point in that chain something could crash out and return a null. It is compact for sure. But really is a pain to debug, but easy to read. Yet a lot of what is going on is burred in the 'middle' what if something in the middle is returning the wrong thing and the next thing in the chain happens to have the right method?
Compact styles can be easy to read sometimes if you know what that style is. You can also very easily introduce very subtle bugs. You can also convey the wrong meaning to the next poor soul that has to look at your code 3 years from now, 2 years after you left the company.
I use this style as sparingly as possible and fall towards verbose and spaced out code. I try to make it easy to read and broken down as best as possible. 6 months from now my tired brain will thank me.
For something like this example if I ended up with an if tree like that I would look at the underlying data structures. There is a data problem here and there probably would be a better way to do it.
I get the sentiment, but I think it's a fundamental error to equate "verbose" with "clear"/"simple"/"readable"/etc. and "compact" with "clever"/"fast"/"difficult"/etc.
tl;dr trying to make things smaller isn't "clever", it's code golf; often, "clever" solutions just-so-happen to end up small. Likewise, spreading logic over many lines can lose abstraction; we can end up lost in a tangle of bools, ints, etc. without seeing the bigger picture.
Do not disagree at all. I almost always lean towards readability so I can make the next poor soul understand it (usually me 6 months from now). I once ended up with one of those 'clever' bits of code. It was the best solution because of the constraint we were in. But a co-worker (who helped create it) put it best 'no damn way are any of us going to be able to figure that out 6 months from now'. He was perfectly right, and it did take me 2 days to untangle it about a year later. That was comment time to for future me to put in what was this thing doing and why it worked the way it did (lesson learned).
Getting that 'terse'/'readable' balance right can be tricky. Usually if some code is 'hard to read' it usually means it needs a bit of refactoring to shorten/length it up and make clear (with comments) what each bit is doing. You go drop something like duffs device into the middle of a parser you should put a comment on that. As not everyone has heard of it. If the code is going to be used a couple of times a year and if it takes an extra 15 seconds, so what. Comment it with 'hey this would be a good spot for duffs device?'. Most of the type of code I write these days runs so rarely and can take a bit of extra time. I am also working with jr devs who may or may not have read up on every cool trick. I am also playing with some code I got from the net. Some of these things have 5 page long functions, yep... totally lost in abstraction.
An ideal that you want to try to attempt to work through is the Principle of Least Power[0].
While is strictly more powerful than for, for is strictly more powerful than foreach, foreach is strictly more powerful than map.
And yet 95% of the time, the power in map is sufficient. Therefore 95% of the time you should use map. When you encounter a foreach, you should be expecting non-purity. When you encounter a while, you know that it's doing some recursive operation that requires that power. If you have junior members of the team writing while loops where maps would do the senior members of the team who understand the nuance will take 10x more time to understand that code.
The same applies to statements/code blocks vs expressions. If all you are doing is assigning one value and have no other side effects, and you can do so in a way that's not overly nested, you should use an expression. If you can't, we have the more powerful statement/block structure to fall back on.
I mean sure, that’s true of every programming language. The reason to follow community standards is to make the lives of other people reading your code easier. Consistency is far more important than any individual’s optimal style.
Which brings up another point -- that's precisely the difference between having the format be essentially part of the language from day one, and shoehorning it in later. The utility of auto formatting comes from instantly eliminating the squabble and bikeshedding.
The article is not representative of the community, quite the opposite.
In fact, the community is embracing "black" these days, the python equivalent of gofmt, exactly because we have better things to do than arguing over PEP 8.
I was thinking back to my earlier days when I'd post questions with code in the freenode python channel and get quibbles about choosing to index an array or not use a list comprehension or which way to format strings. I only ever really write python for quick scripts for myself these days anyway so I guess it's a moot point. Maybe I'll look into what black formatting looks like though.
scale = 5
# The alternative would be exponentials with range(),
# but it's clearer to use 'while'
# pylint: disable=[while-used]
while (scale > 0.1):
do_stuff(scale)
scale = scale/2
Less sarcastically, how about:
scales = itertools.takewhile(
lambda n: n > 0.1,
functools.reduce(lambda x, y: x / y, itertools.repeat(2), 5)
)
map(do_stuff, scales)
This would be even easier with an 'iterate(x, f)' function which generated (x, f(x), f(f(x)), ...), but I couldn't find one in Python's builtins:
scales = takewhile(
lambda n: n > 0.1,
iterate(5, lambda x: x / 2)
)
map(do_stuff, scales)
Indeed, though in a language designed for this style of programming, the idea behind that final example in the GP comment works fine. A Haskell version might look something like this:
The `iterate` generates the infinite sequence, the `takeWhile` specifies how much of it to use, and then we do stuff with each element.
This is arguably a little nicer than the while loop since it exposes the real sequence of values you’re working with more explicitly, but at the very least it is comparably simple in its appearance. The Python version in the GP comment looks bad mostly because of the clumsy lambda syntax.
I shouldn't need to read the code extremely closely or repeatedly to have to figure out what it does when the while loop does it clearly (and in a bounded way that you can literally mathematically prove).
Also, I would scream if some opinionated dev gone crazy with their linter added pylint disables and comments explaining it every time we use a while loop in our codebase.
Why does the linter rule not, instead, check if the while loop is unbounded and warn the user of that? Surely screaming fire when there's an actual fire is better than screaming fire at the first sign of smoke.
> I would scream if some opinionated dev gone crazy with their linter added pylint disables and comments explaining it every time we use a while loop in our codebase.
That sounds like a bad situation, but it completely depends on "every time we use a while loop". The entire point of lint checks is to reduce the occurrences of certain patterns (like while loops, in this case). This post is arguing that "every time we use a while loop" should ideally be the same as "never", in which case you're still correct, but vacuously so.
For a less controversial example, try running your comment through sed 's/while loop/eval/g'. There are certainly situations where 'eval' is the only way to do something; and other situations where 'eval' would be more readable/efficient (e.g. compared to writing a file and spawning a subprocess). Yet we go out of our way to minimise our reliance on 'eval', and I certainly wouldn't mind adding a pylint-disable comment for those times I use it (every few years).
Or you can use a for to satisfy the linter that is more in the spirit of the while loop, albeit at a cost of O(n) memory where n is the number of iterations the loop takes:
scale = 5
t = [1]
for i in t:
do_stuff(scale)
scale = scale/2
if scale > 0.1:
t.append(1)
Ironically (to me at least, because it's a construct I use -far- more rarely than while), this actually seems like a case where a C-style for loop might actually be nicer:
The author mentioned this at the end, but banning while loops (and any other way for running a loop with a priori unknown number of iterations) actually decreases the expressiveness of a language - it becomes no longer Turing complete. A classic example is the Ackerman function - it cannot be computed in a primitive recursive language.
(The author is on point when they say almost all code used in practice is primitive recursive, though)
It doesn't have to be a while loop. Recursion is another way to implement a loop with a priori unknown number of iterations. You can also use goto.
And no, they aren't othogonal. A simple language with only integer variables, basic arithmetic, if() and for(i in range(x,y)) is actually not Turing complete.
i think the key point is that the linter doesn't ban anything. nobody is proposing removing while statements from the language. if you have a valid case for disobeying the linter, then you can disable the linter for that line: https://pylint.pycqa.org/en/latest/user_guide/message-contro...
(if a project unconditionally bans PRs that don't pass the linter, that's a different issue, and not really the linter's fault)
I could certainly see a linter that says "are you sure?" before you initially add a while loop to make you ask yourself if that's really the best option being quite neat.
The trick would be making it sufficiently useful and minimally annoying that people don't get angry and turn it off.
it's not really the linter's job to say "are you sure". the linter just applies the rules and generates messages, it's up to you to do with those messages what you will
when using the python integration in vscode (which runs pylint), clicking on a linter error gives me a context menu where one of the options is essentially "are you sure?" and will automatically adds the appropriate comment to disable the linter for that line.
> less code is better than more code, and this block can be rewritten more concicely
Conciseness is not necessarily better.
- Does this idiom make debugging easier? no, it doesn't.
- Does this idiom make it easy to identify where a branch starts and ends? no, it doesn't. Before you could use indentation alone, now you have to read the statement.
- Is it slightly faster to type? Maybe. But that does not matter because time spent typing is a tiny tiny fraction of the time you spend as a developer. You spend much more time reading than typing.
Python doesn't have a do..while loop, so when you want to do a thing at least once, the simplest replacement is starting the loop with "while 1:" and ending the loop with "if ... break".
Because "while 1:" is valid Python, but "while true:" is not
;) (gotta capitalize "True").
In addition to the GP's self deprecating sibling comment, I started using Python around 2.2, before there were even "True" and "False". So, seeing a 'while 1' loop is perfectly natural to me. But, I'm also perfectly comfortable with Python's "truthiness" in more places than most people are.
As a bonus: consider "while 'false' :" as a perfectly valid start to an unbounded loop.
> consider "while 'false' :" as a perfectly valid start to an unbounded loop
That sort of thing is ubiquitous in dynamically-typed languages. Even HTML: the state of boolean attributes is determined by the attribute being set, regardless of its value, so <input disabled=false> will give you a nice disabled text box. Mind you, some attributes that you might think would be boolean actually aren’t, for varying reasons good and bad, e.g. autocomplete=on|off, aria-hidden=true|false.
I learned Python around that time, when the docs said that `open()` is deprecated and to use `file()` as a constructor instead. Still unlearning that habit.
Python doesn't have an “[repeat...]until” loop, which C misspells as “do...while”, which fails to express what is going on.
> so when you want to do a thing at least once, the simplest replacement is starting the loop with "while 1:" and ending the loop with "if ... break".
“while True:” is more idiomatic Python (“while 1:” works since 1 is truthy, but using a literal 1 for “True” is a C-ism, Python has True as a literal for quite some time and idiomatic Python uses it.)
While I agree with the ternary linting, the while loop piece makes absolutely no sense. There are absolutely many, many use cases of while loops; in fact, many dynamic programming algorithms rely on them.
Could you write them as a for loop? Possibly, but why? Is the new 2021 programming fad hating on while loops? Who do I contact to exchange my "js bad" t-shirts for "while bad" laptop stickers?
The logic behind disapproving of while loops in the article seems to stem from the author assuming while loops are "unbounded." This is absolutely, completely, totally false and there are entire branches of reasoning dedicated towards observing the bounded-ness of while loops. Most colleges include this in their curriculums and you usually have to learn to prove (via induction) the variants and invariants surrounding your loop before you can pass basic classes. It's even more ridiculous that, in the same breath, the author assumes for loops are guaranteed to complete execution. I can disprove this in two lines of code and two braincells:
for x in infinite_generator():
do_something()
This is also the general structure that message-queue libraries like Kafka, RabbitMQ etc take with their python code. Not to mention that, in many programming languages, for-loops are actually implemented as while loops behind the scenes.
I'll cede that it's easier to write unbounded code with while loops than for loops, but this is programmer error that can be pretty easily avoided by simply being a teensy tiny bit careful (and can be easily corrected). I'd also argue that the majority of these cases where while loops can be substituted by for loops, are also cases where writing a while loop is much simpler than trying to convert into a for loop. (Besides, you could make any of these arguments about recursion.)
> You know what else doesn’t have unbounded loops? Excel.
This is also a lie, Excel absolutely has infinite loops, it just yells at you when you do it. The equivalent of this in programming is a linter. Instead of just screaming fire every time the user writes a perfectly fine while loop, why not concentrate your efforts on identifying unbounded loops and introducing a linter rule for that?
Honestly, it's absolutely terrifying to me that someone contributing to the predominant linter for a major programming language not only wholeheartedly believes while loops are always unbounded and usually evil, but also managed to get this introduced (and presumably approved) by reviewers of this linter, and then proceeded to gloat about it on HackerNews. It comes as no shock to me that most people I've worked with disable pylint in their editors if they're this unreliable with their review process.
I think there is a place for it, though it was poorly justified in the article.
It depends on the type of programming you’re doing.
Algorithmics routinely does stuff that matches while loops and C-style for loops while loops, but doesn’t match iterator for loops very well.
In most parts of line-of-business sort of software development (and that’s the significant majority of software development, frankly, certainly a lot bigger than algorithmics), I’d say it’s rare for a while loop to be desirable: it should almost always be iterator-powered instead.
I think the C-style for loop thing is a particularly interesting aspect to this: I suspect most code that uses while loops where an iterator would not be appropriate would actually be at least as well-served by a C-style for loop. But Python made a deliberate decision not to have C-style for loops, because most uses of them are better-served by iteration. And so this nudging from while to for is really a perfectly natural extension of that.
Still, all things being considered, it’s not the sort of lint that I would ever turn on for myself or for code that I review, because I know when to use each.
I really hope that the condescending tone of the article was because the author was tired or something, and that it's not the attitude they use when contributing to a linter. The authors comes out as arrogant and self-centered, the "reactions" are strawmans, the tone is aggressive.
Sure that nice Beeping Busy Beaver uses only one loop, very cool. The "guessing game" program that almost everyone wrote when learning programming also uses one loop.
I'll now go work on my linter for blog posts that forbids using bold text. The Bible didn't use any bold text, so why would a simple blog post need it?
> The "guessing game" program that almost everyone wrote when learning programming also uses one loop.
Just because it’s used by very beginners it doesn’t mean it’s good. That’s, like, the opposite of the truth. Beginners also clutter their code with endless “else if” sequences.
You just complained about strawmans and followed with a strawman.
> Just because it’s used by very beginners it doesn’t mean it’s good. That’s, like, the opposite of the truth.
I don't remember saying anywhere that this guessing game was good. You're putting words into my mouth (keyboard?) here. What I meant to say was that saying that the Beeping Busy Beaver only needs one loop doesn't add anything to the discussion, since a simple guessing game also needs one loop. Quoting the article:
> If you have a while loop and you are not working on something that requires unbounded computation, chances are that it can be rewritten more clearly as a for loop. I ran this check against the Pylint codebase itself, and I was shocked by the hideousness of some of the while’s that turned up. They couldn’t all be rewritten, but many could.
> Even the recently-discovered Beeping Busy Beaver champion program only uses one unbounded while loop, and that’s as a kind of toplevel program driver.
Here's how I would put it, in a kinder and more humble way:
"Most of the code I write only needs one main loop, and usually I'm not the one writing it. When I'm using something like Django, I have no need for a while loop, as it's already handled by the framework. In that case, I want the linter to highlight these loops. They are a potential danger, and something I want to pay special attention to. Even when I have to write myself that loop, I want to clearly separate the main loop from what runs into it. In my experience, this leads to code that is more readable, more maintanable, and that has less bugs. Thus, this is a good linter rule."
> Beginners also clutter their code with endless “else if” sequences.
Well, it's not like they can use a switch, considering it's Python. Or are you referring to pattern matching? Or you think using a dictionnary would be more idiomatic? I'm not sure what you're trying to say here.
The part about the Bible and bold text was a parody of the article to quickly highlight what's wrong with it. I personally dislike bold text. I think people shouldn't use it most of the time. Thus I did the same thing as in the article: I used an appeal to authority (the Bible, equivalent to the Beeping Busy Beaver) to say that everything smaller than the Bible shouldn't even use bold text, as the Bible doesn't need any.
Maybe the tone is fully representative. One could start to wonder what kind of people would be attracted to working on linters. 'Now I get to tell everybody what is good or bad'.
I don't think we should extrapolate to what kind of people work on linters or even on how the author thinks. I think the article and the contributions both come from good intentions, and I even agree with the two linting rules. What I don't like at all is the tone of the article. Part of the role of linters is education, and education shouldn't be done with that tone.
Hah. Once-upon a time, I relied on bold text a lot for emphasis. Then I learned that its a crutch for bad writing, so I made an effort to use it very sparingly. I think my written material has improved substantially because of it. I think keeping bold text to a minimum is a good rule to follow when writing.
Then so does a for loop. There’s no difference in this regard between for and while loops: you can use both of them to express both bounded and unbounded computations, and in each case you need to read and understand at least what comes before the colon to know which it is.
# Unbounded
while True:
pass
# Bounded
while False:
pass
# Unbounded
for i in itertools.count():
pass
# Bounded
for x in []:
pass
And you can’t just say “but with for loops you only need to worry about the iterator, whereas with while loops you’ll probably need to worry about statements inside the loop in addition to the loop condition”, as seen in this entirely realistic function that is unbounded if source == target (assuming a port of the DOM API, so child_nodes is a live collection):
def clone_children_to(source: Element, target: Element):
for node in source.child_nodes:
target.append_child(node.clone())
(I agree that for loops are generally preferable to while loops where feasible, but I object to the way you’ve expressed that paragraph, and the expressed reasoning underpinning the entire section is flawed, as priansh also points out.)
If you enable it. And if you really care about avoiding while-loops, you can still silence it for the ones you really want to have (e.e. main loops like your example, as also mentioned in the article).
In practice, quitRequested and processNextEvent are probably both members of some managerial object; perhaps like this:
while not loop.quit_requested:
process(loop.next_event())
And when you see it like that, then it becomes more apparent there’s an obvious way for it to be an iterator:
for event in loop.run():
process(event)
This is typically harder to get wrong, and generally matches the semantics of what you’re trying to do more closely: process a stream of events. You didn’t actually care about quit_requested, you cared about the events.
And so in this you can start to see why iterator-based for loops are normally better than while loops (and C-style for loops, which Python doesn’t support): while loops (and C-style for loops) are generic bookkeeping, with no specific semantics on the while condition (or for init/condition/increment clauses); but iterator-based for loops operate on actual data.
This principle can be seen in resource locking also; you don’t want a lock object beside the data that it locks, like this:
with lock.acquire():
queue.append(item)
That style is just asking for trouble; sooner or later you’ll touch the queue without acquiring the lock, and everything will fall apart. Instead, you want to lock the actual data so that you can’t even access the locked thing without acquiring the lock:
with queue.lock() as q:
q.append(item)
P.S. If you deal with something like the original loop without a run() method, you can write a generator to convert a while loop into a for loop:
def run(loop):
while not loop.quit_requested:
yield loop.next_event()
for event in run(loop):
process(event)
Most of the time the difference won’t be significant or worth it, but sometimes this can really help to clarify things, by extracting the loop bookkeeping into one place so that you can do what you were semantically trying to do, processing a stream of events.
P.P.S. Instead of range(999999), use itertools.count() when you want unbounded iteration. Or itertools.repeat(None) if you don’t care about the number—but in that unused number or None see that a for loop is probably not the right tool for what you’ve written: you would be better either shifting back to while loops, or iterating over the data as I’ve demonstrated.
I usually do some sort of maximum recursion check. For things where I’d expect for example max 10 levels, I’d check for 1000-10000, and then raise an exception.
> You know what else doesn’t have unbounded loops? Excel.
Right, one of the many reasons why nobody (with half a brain) hosts a web server in Excel.
I wonder how much "boring web app" experience the author had. If I'm debugging on 11 pm why the webserver is timing out talking to microservice A, but only if it first opened connection to service B, and someone strolls along saying "Hey, your code is bad because it's using unbounded computation," then god help us, because I might lose it. (Well, shrug, the worst that can happen is that I may rage-quit on the spot. I'm not a very imposing guy.)
That's why you want to start thinking macro and holding your code to a high standard early. You can, for the most part, avoid firefighting if you don't let things fester.
Well, sure, you're absolutely right that it's best to write high-quality code so that you avoid firefighting in the first place. But that's an orthogonal question to whether the number of while loops is relevant to code quality. Most programs (or even subtasks) aren't Turing machines - they are "unbounded" in that they're supposed to run indefinitely until someone tells them to quit, or until they abruptly hit EOF, end of stream, whatever. You don't improve such logic by making them "bounded" - it will be in fact missing the point.
* If you prefer, change 11 pm to 11 am and my point still stands (though I'll be less cranky and less likely to rage-quit).
While I appreciate the idea of bounded loops, it's a mistake to think that a `for` loop is necessarily bounded.
It's pretty easy to write a generator that produces an infinite sequence. That generator, of course, would need a `while` loop, but that loop can be in an external dependency which the linter is not checking.
You also can read way more than you expected if you use a `for` loop to read from a socket, or a file, to say nothing about reading from /dev/random.
Not that it's a bad advice, but it's not a rule, it's more like a... guideline. Discretion is still needed.
You should be able to create an unbounded for loop as well. After all, a python process has mutable state, so it should be fairly simple to increment the length of your loop inside the loop body.
Or bounded… by the heat death of the universe. I think there’s a point though, if you’re writing a stack machine or something like that where it makes sense, you’d know. But many novice programmers reach for the old while-bool-var construct and set it to false in the loop body (and often reset it to true later. So confusing. )
Unbounded computation is necessary when working on either event driven architecture (block/wait for something, run it, block/wait again) or working with frames.
I can’t imagine making a game (though not sure who makes games in python) without a while loop to run on the main thread.
The ootb linter setups I've seen offer little useful checks, just everybodies feelings about your code, so personally don't find them useful. This article is a prrfect example of that, but kudos to the author for being honest about it.
I’m ~intermediate at python and understand the arguments in this article, but the comments are so negative and varied that i don’t know how to make sense of the contrary. Could somebody please share some more advanced resources about while loops/for loops/iterables, or point me in the right direction?
My understanding is that the blog post is a bit too opinionated, dismissing valid uses of language features with a perceived condescending tone.
The ternary operator is mostly a matter of style, and just be aware that for loops is often a better way to iterate through things than while loops. I wouldn't worry too much.
261 comments
[ 3.0 ms ] story [ 292 ms ] threadI'd find that pretty annoying.
Consider a while loop in a thread:
I also think there were good reasons the author's linters were left as optional, and its probably not because everyone thought they were great ideas.
This trope needs to die. While loops are a basic language feature. A thread pool is one very specific example of where that language feature is necessary. Every very specific example is trivially dismissed with "the vast majority of of people aren't doing that very specific thing." It's the laziest rebuttal, and not even wrong.
Cool things I've seen written in python that need while loops: http servers, games, more generally anything that listens on a port or depends on user input, mathematical code that depends on computing a base-k representation, stack-based algorithms...
Hold on, let's stop there. Python sucks at recursion. So bad it's capped at a very small depth. The only reasonable workaround is to avoid the call stack and roll your own, which involves... you guessed it, a while loop. So, this linter prefers buggy stack-smashy code. That's nice. Adults can use another linter.
Not when it's at the cost of readability. The example "better" code fails my readability test horribly. I'd gladly take C's ternary operator over this monstrosity:
> x = 4 if condition() else 5
So you can read both as a sentence. The arguable part is more about "condition and then thing" versus "thing if condition".
It really is clearest and safest to avoid inconsistent state, even if is only transient. There are so many ways programs can be wrong; no need to deliberately create inconsistent state when there's no need to.
Also, your version isn't safe to use with objects (e.g. 'myObject.x = ...'), since the initial assignment could trigger arbitrary code (properties, __setattr__, etc.).
Also, your version isn't safe to use when the right-hand-side has effects, e.g.
> Special cases aren't special enough to break the rules.
> There should be one-- and preferably only one --obvious way to do it.
Both are broken by the existance of 2 if syntaxes depending on the context.
Infix "if" conditions just scramble up the order of evaluation from the order of program source code.
If the condition-guarded statement is nontrivial and you remove the assignment, everything stays valid. If otoh you use a ternary or
If you now delete the else block or something (like the assignment from a larger else block), a linter can warn you that y is undefined. Otherwise you'd need a test to ensure correctness.$user_authenticated = hmac_auth($_get['token']) === true || ldap_check($username, $config['ldap_restrictions']) || verify_credentials($_post['user'], $_post['pass']) === true;
Sorry for the php pseudo code, I'm on mobile. And this is a very friendly example of what I'm trying to get across. It's self documenting code and worth the extra bytes.
I've seen way too many insane conditionals to agree that less code is better.
For the cost of 4 extra characters, this line tells you exactly what it does even if you've never seen Python in your life.
I wouldn't mind it so much if I could write it as:
Yes, that is a semicolon. Fight me.Makes way more sense to my brain to read:
print unless $x ~ /end$/;
then
print if $x !~ /end$/;
I used to get flack for using not just if expressions as ternaries, but also for using unless. Then I started teaching PERL at my company and drilled it into all the fresh new minds.
Also doing that with if/elsif/elsif/else is often far more readable than nested/chained ternaries.
Or in pseudocode
Even ternaries read weird. "css class is current tab HUH?? selected COLON! deselected"if condition(): x=4 else: x=5
I get that it's a "flex" or some sort, but honestly in production code my experience is that it reduces productivity. Programmers need a little more squinting to truly understand what that piece of code is doing.
I find it similar to run-on sentences in books - we don't like that, and in Business Writing courses they explicitly say to not do that. Code should be similarly readable.
So...you should do more per line, since code is all one-liners, its just a choice of how many, so if you don't like them, you should reduce the number?
Though Ruby’s unless modifier is often slightly better for readability.
Python's "Black" formatter actually does the best job here, yet the python ternary syntax is still very verbose and strange IMO.
Prettier does it fine for JS, which uses C-style ternary syntax.
> Python's "Black" formatter actually does the best job here, yet the python ternary syntax is still very verbose and strange IMO.
To me, its quite natural when used sensibly, since if you drop everything after the if it is the normal-case value. Though I would slightly prefer if the ternary form was:
instead of:> Though I would slightly prefer if the ternary form was:
Agreed, I do like that much better too.
When I started learning ruby a bunch of the changes flew over my head, but it is also an excellent entry point to look further into best practices, and concrete examples of how my code could be better.
The condition is of course the tool being itself extremely good, otherwise it's just everyday hell (looking at you, phpstan...)
If you think expecting people to achieve Python mastery is unreasonable, wait until I tell you about language called C++.
I would never demand this for someone writing a Python app or module that out team would use as a normal import, just the stuff we actively manage.
Probably the whole concept of idiomatic Python is that there shouldn't be different styles of Python to which you graduate according to skill or experience, but rather that the simple, readable way of writing things should be done by everyone, always, from novices to experts. Code-golfing with dense weirdness would be "anti-Pythonic" but this ternary expression would be readable even for if someone who's never seen Python would read it as pseudocode in some theoretical CS paper.
There are lots of examples where ternary makes things obviously harder to read, such as when at least two of the three expressions are non-trivial. Which one would you rather read?
or (or after Black.)There is a very simple rule avoiding most of these issues - if the ternary expression does not fit in one line, use if/else.
This is one of the first things that I add to a new project, before I write one line of code: https://github.com/realm/SwiftLint
I write about it here: https://littlegreenviper.com/miscellany/swiftwater/swiftlint...
If the linter says your function is too long then you refactor it, you don't turn off the linter. You also fix it immediately, not "later" (broken windows theory) before the next offender copies and pastes the offending code or continues making it worse.
In underdeveloped countries, governments manipulate the definition of unemploment to make themselves look better. Does that solve anything? no. It actually makes things worse because now you cannot mobilize people and resources to solve a problem you don't have. It's wrong. And so is turning off linting.
The nice thing about ternaries is that they're expressions, so they don't "infect" our code like statements (e.g. 'if'). For example:
This lint rule will tell us to do the following instead: Now that our branching is done with expressions rather than statements, we can actually in-line them, which seems much nicer (less chance to get them mixed up; no need to invent new names; no side-effects, e.g. accidentally overwriting an existing variable; etc.) If we really want the names, we could define them using the 'walrus operator': Personally I would use separate 'x = ...' statements rather than :=, since we want to explicitly perform effects (binding names).Yes, that's what I intended. I was pointing out that the foo(x1 if ...) version does not behave the same as the separate 'x = ...' statements, since it doesn't bind the x/y/z variables.
This is usually an advantage (if we don't need those values for anything else), but for completeness I noted that we could use the walrus to recreate the exact behaviour (keyword arguments can't do it, since they only bind variables inside the function call, which (usually) has no observable difference to using positional arguments). You're right that the walrus's effect on the surrounding namespace is 'surprising', and that's why I would prefer to use separate statements if we really want those names defined (since that's less surprising).
Sure in small cases this small ternary use is great! However too many times I've seen people chain them for far too many characters just to be "on one line".
We don't use one letter variable names anymore, so in the same reasoning why should we do the same to our code?
I strongly disagree with the author's idea that shorter is better. Clarity trumps conciseness. I'll admit that not enough conciseness can impact clarity, but there's ways and means to clean that up that don't involve clever code.
To test whether those statements are guiding principles, or merely ad-hoc justifications, I propose the following pylint rules based on those principles:
Of course, the combination of no-elif-sugar and no-special-case-patterns would give extreme clarity like this:Everyone has their own 'style' they like. It is usually not that big of a deal. It becomes a big deal if you get someone on the team who becomes obsessed with it, or someone who is always sloppy. Compact styles tend to be harder to decipher than simple ones. As when you are reading them they usually are not the same style context as all the other code around it. So it causes your brain to have to stop and figure it out. With python sometimes those compact styles actually run faster, so they can be handy to know about (test it though).
In java there is this thing where many will do things like blah = x().y().z(); Yet at any point in that chain something could crash out and return a null. It is compact for sure. But really is a pain to debug, but easy to read. Yet a lot of what is going on is burred in the 'middle' what if something in the middle is returning the wrong thing and the next thing in the chain happens to have the right method?
Compact styles can be easy to read sometimes if you know what that style is. You can also very easily introduce very subtle bugs. You can also convey the wrong meaning to the next poor soul that has to look at your code 3 years from now, 2 years after you left the company.
I use this style as sparingly as possible and fall towards verbose and spaced out code. I try to make it easy to read and broken down as best as possible. 6 months from now my tired brain will thank me.
For something like this example if I ended up with an if tree like that I would look at the underlying data structures. There is a data problem here and there probably would be a better way to do it.
I see this so often that I wrote a blog post about it http://chriswarbo.net/blog/2020-02-08-clever_code.html
tl;dr trying to make things smaller isn't "clever", it's code golf; often, "clever" solutions just-so-happen to end up small. Likewise, spreading logic over many lines can lose abstraction; we can end up lost in a tangle of bools, ints, etc. without seeing the bigger picture.
Getting that 'terse'/'readable' balance right can be tricky. Usually if some code is 'hard to read' it usually means it needs a bit of refactoring to shorten/length it up and make clear (with comments) what each bit is doing. You go drop something like duffs device into the middle of a parser you should put a comment on that. As not everyone has heard of it. If the code is going to be used a couple of times a year and if it takes an extra 15 seconds, so what. Comment it with 'hey this would be a good spot for duffs device?'. Most of the type of code I write these days runs so rarely and can take a bit of extra time. I am also working with jr devs who may or may not have read up on every cool trick. I am also playing with some code I got from the net. Some of these things have 5 page long functions, yep... totally lost in abstraction.
While is strictly more powerful than for, for is strictly more powerful than foreach, foreach is strictly more powerful than map.
And yet 95% of the time, the power in map is sufficient. Therefore 95% of the time you should use map. When you encounter a foreach, you should be expecting non-purity. When you encounter a while, you know that it's doing some recursive operation that requires that power. If you have junior members of the team writing while loops where maps would do the senior members of the team who understand the nuance will take 10x more time to understand that code.
The same applies to statements/code blocks vs expressions. If all you are doing is assigning one value and have no other side effects, and you can do so in a way that's not overly nested, you should use an expression. If you can't, we have the more powerful statement/block structure to fall back on.
[0]: https://blog.codinghorror.com/the-principle-of-least-power/
It was interesting to find out how HTML was designed from the start to be simple and not a programming language on purpose for this reason!
Guido doesn't like it, though, but the community seems to.
In fact, the community is embracing "black" these days, the python equivalent of gofmt, exactly because we have better things to do than arguing over PEP 8.
This is arguably a little nicer than the while loop since it exposes the real sequence of values you’re working with more explicitly, but at the very least it is comparably simple in its appearance. The Python version in the GP comment looks bad mostly because of the clumsy lambda syntax.
Also, I would scream if some opinionated dev gone crazy with their linter added pylint disables and comments explaining it every time we use a while loop in our codebase.
Why does the linter rule not, instead, check if the while loop is unbounded and warn the user of that? Surely screaming fire when there's an actual fire is better than screaming fire at the first sign of smoke.
Indeed, the linter only needs to incorporate of the many known solutions to the halting problem.
That sounds like a bad situation, but it completely depends on "every time we use a while loop". The entire point of lint checks is to reduce the occurrences of certain patterns (like while loops, in this case). This post is arguing that "every time we use a while loop" should ideally be the same as "never", in which case you're still correct, but vacuously so.
For a less controversial example, try running your comment through sed 's/while loop/eval/g'. There are certainly situations where 'eval' is the only way to do something; and other situations where 'eval' would be more readable/efficient (e.g. compared to writing a file and spawning a subprocess). Yet we go out of our way to minimise our reliance on 'eval', and I certainly wouldn't mind adding a pylint-disable comment for those times I use it (every few years).
(The author is on point when they say almost all code used in practice is primitive recursive, though)
And no, they aren't othogonal. A simple language with only integer variables, basic arithmetic, if() and for(i in range(x,y)) is actually not Turing complete.
(if a project unconditionally bans PRs that don't pass the linter, that's a different issue, and not really the linter's fault)
The trick would be making it sufficiently useful and minimally annoying that people don't get angry and turn it off.
when using the python integration in vscode (which runs pylint), clicking on a linter error gives me a context menu where one of the options is essentially "are you sure?" and will automatically adds the appropriate comment to disable the linter for that line.
Conciseness is not necessarily better.
- Does this idiom make debugging easier? no, it doesn't.
- Does this idiom make it easy to identify where a branch starts and ends? no, it doesn't. Before you could use indentation alone, now you have to read the statement.
- Is it slightly faster to type? Maybe. But that does not matter because time spent typing is a tiny tiny fraction of the time you spend as a developer. You spend much more time reading than typing.
I disapprove of any linter that flags this idiom.
In addition to the GP's self deprecating sibling comment, I started using Python around 2.2, before there were even "True" and "False". So, seeing a 'while 1' loop is perfectly natural to me. But, I'm also perfectly comfortable with Python's "truthiness" in more places than most people are.
As a bonus: consider "while 'false' :" as a perfectly valid start to an unbounded loop.
That sort of thing is ubiquitous in dynamically-typed languages. Even HTML: the state of boolean attributes is determined by the attribute being set, regardless of its value, so <input disabled=false> will give you a nice disabled text box. Mind you, some attributes that you might think would be boolean actually aren’t, for varying reasons good and bad, e.g. autocomplete=on|off, aria-hidden=true|false.
http://python-history.blogspot.com/2013/11/the-history-of-bo...
https://www.python.org/dev/peps/pep-0285/
Python doesn't have an “[repeat...]until” loop, which C misspells as “do...while”, which fails to express what is going on.
> so when you want to do a thing at least once, the simplest replacement is starting the loop with "while 1:" and ending the loop with "if ... break".
“while True:” is more idiomatic Python (“while 1:” works since 1 is truthy, but using a literal 1 for “True” is a C-ism, Python has True as a literal for quite some time and idiomatic Python uses it.)
Could you write them as a for loop? Possibly, but why? Is the new 2021 programming fad hating on while loops? Who do I contact to exchange my "js bad" t-shirts for "while bad" laptop stickers?
The logic behind disapproving of while loops in the article seems to stem from the author assuming while loops are "unbounded." This is absolutely, completely, totally false and there are entire branches of reasoning dedicated towards observing the bounded-ness of while loops. Most colleges include this in their curriculums and you usually have to learn to prove (via induction) the variants and invariants surrounding your loop before you can pass basic classes. It's even more ridiculous that, in the same breath, the author assumes for loops are guaranteed to complete execution. I can disprove this in two lines of code and two braincells:
This is also the general structure that message-queue libraries like Kafka, RabbitMQ etc take with their python code. Not to mention that, in many programming languages, for-loops are actually implemented as while loops behind the scenes.I'll cede that it's easier to write unbounded code with while loops than for loops, but this is programmer error that can be pretty easily avoided by simply being a teensy tiny bit careful (and can be easily corrected). I'd also argue that the majority of these cases where while loops can be substituted by for loops, are also cases where writing a while loop is much simpler than trying to convert into a for loop. (Besides, you could make any of these arguments about recursion.)
> You know what else doesn’t have unbounded loops? Excel.
This is also a lie, Excel absolutely has infinite loops, it just yells at you when you do it. The equivalent of this in programming is a linter. Instead of just screaming fire every time the user writes a perfectly fine while loop, why not concentrate your efforts on identifying unbounded loops and introducing a linter rule for that?
Honestly, it's absolutely terrifying to me that someone contributing to the predominant linter for a major programming language not only wholeheartedly believes while loops are always unbounded and usually evil, but also managed to get this introduced (and presumably approved) by reviewers of this linter, and then proceeded to gloat about it on HackerNews. It comes as no shock to me that most people I've worked with disable pylint in their editors if they're this unreliable with their review process.
It depends on the type of programming you’re doing.
Algorithmics routinely does stuff that matches while loops and C-style for loops while loops, but doesn’t match iterator for loops very well.
In most parts of line-of-business sort of software development (and that’s the significant majority of software development, frankly, certainly a lot bigger than algorithmics), I’d say it’s rare for a while loop to be desirable: it should almost always be iterator-powered instead.
I think the C-style for loop thing is a particularly interesting aspect to this: I suspect most code that uses while loops where an iterator would not be appropriate would actually be at least as well-served by a C-style for loop. But Python made a deliberate decision not to have C-style for loops, because most uses of them are better-served by iteration. And so this nudging from while to for is really a perfectly natural extension of that.
Still, all things being considered, it’s not the sort of lint that I would ever turn on for myself or for code that I review, because I know when to use each.
Sure that nice Beeping Busy Beaver uses only one loop, very cool. The "guessing game" program that almost everyone wrote when learning programming also uses one loop.
I'll now go work on my linter for blog posts that forbids using bold text. The Bible didn't use any bold text, so why would a simple blog post need it?
Just because it’s used by very beginners it doesn’t mean it’s good. That’s, like, the opposite of the truth. Beginners also clutter their code with endless “else if” sequences.
You just complained about strawmans and followed with a strawman.
I don't remember saying anywhere that this guessing game was good. You're putting words into my mouth (keyboard?) here. What I meant to say was that saying that the Beeping Busy Beaver only needs one loop doesn't add anything to the discussion, since a simple guessing game also needs one loop. Quoting the article:
> If you have a while loop and you are not working on something that requires unbounded computation, chances are that it can be rewritten more clearly as a for loop. I ran this check against the Pylint codebase itself, and I was shocked by the hideousness of some of the while’s that turned up. They couldn’t all be rewritten, but many could.
> Even the recently-discovered Beeping Busy Beaver champion program only uses one unbounded while loop, and that’s as a kind of toplevel program driver.
Here's how I would put it, in a kinder and more humble way:
"Most of the code I write only needs one main loop, and usually I'm not the one writing it. When I'm using something like Django, I have no need for a while loop, as it's already handled by the framework. In that case, I want the linter to highlight these loops. They are a potential danger, and something I want to pay special attention to. Even when I have to write myself that loop, I want to clearly separate the main loop from what runs into it. In my experience, this leads to code that is more readable, more maintanable, and that has less bugs. Thus, this is a good linter rule."
> Beginners also clutter their code with endless “else if” sequences.
Well, it's not like they can use a switch, considering it's Python. Or are you referring to pattern matching? Or you think using a dictionnary would be more idiomatic? I'm not sure what you're trying to say here.
The part about the Bible and bold text was a parody of the article to quickly highlight what's wrong with it. I personally dislike bold text. I think people shouldn't use it most of the time. Thus I did the same thing as in the article: I used an appeal to authority (the Bible, equivalent to the Beeping Busy Beaver) to say that everything smaller than the Bible shouldn't even use bold text, as the Bible doesn't need any.
Hah. Once-upon a time, I relied on bold text a lot for emphasis. Then I learned that its a crutch for bad writing, so I made an effort to use it very sparingly. I think my written material has improved substantially because of it. I think keeping bold text to a minimum is a good rule to follow when writing.
Then so does a for loop. There’s no difference in this regard between for and while loops: you can use both of them to express both bounded and unbounded computations, and in each case you need to read and understand at least what comes before the colon to know which it is.
And you can’t just say “but with for loops you only need to worry about the iterator, whereas with while loops you’ll probably need to worry about statements inside the loop in addition to the loop condition”, as seen in this entirely realistic function that is unbounded if source == target (assuming a port of the DOM API, so child_nodes is a live collection): (I agree that for loops are generally preferable to while loops where feasible, but I object to the way you’ve expressed that paragraph, and the expressed reasoning underpinning the entire section is flawed, as priansh also points out.)Someone needs to inform this guy that generators exist
> anything that runs for as long as the user says it should run;
Per the article...
Probably not for me though.
And so in this you can start to see why iterator-based for loops are normally better than while loops (and C-style for loops, which Python doesn’t support): while loops (and C-style for loops) are generic bookkeeping, with no specific semantics on the while condition (or for init/condition/increment clauses); but iterator-based for loops operate on actual data.
This principle can be seen in resource locking also; you don’t want a lock object beside the data that it locks, like this:
That style is just asking for trouble; sooner or later you’ll touch the queue without acquiring the lock, and everything will fall apart. Instead, you want to lock the actual data so that you can’t even access the locked thing without acquiring the lock: P.S. If you deal with something like the original loop without a run() method, you can write a generator to convert a while loop into a for loop: Most of the time the difference won’t be significant or worth it, but sometimes this can really help to clarify things, by extracting the loop bookkeeping into one place so that you can do what you were semantically trying to do, processing a stream of events.P.P.S. Instead of range(999999), use itertools.count() when you want unbounded iteration. Or itertools.repeat(None) if you don’t care about the number—but in that unused number or None see that a for loop is probably not the right tool for what you’ve written: you would be better either shifting back to while loops, or iterating over the data as I’ve demonstrated.
For other languages, you might still hit stack overflow (not the site) before you get to 10000 if the stuff you push on the stack is large-ish.
Right, one of the many reasons why nobody (with half a brain) hosts a web server in Excel.
I wonder how much "boring web app" experience the author had. If I'm debugging on 11 pm why the webserver is timing out talking to microservice A, but only if it first opened connection to service B, and someone strolls along saying "Hey, your code is bad because it's using unbounded computation," then god help us, because I might lose it. (Well, shrug, the worst that can happen is that I may rage-quit on the spot. I'm not a very imposing guy.)
If you're in charge, that is...
* If you prefer, change 11 pm to 11 am and my point still stands (though I'll be less cranky and less likely to rage-quit).
They love linters, you sound like you hate linters. Don’t use them and let your colleagues deal with your unbounded computation.
It's pretty easy to write a generator that produces an infinite sequence. That generator, of course, would need a `while` loop, but that loop can be in an external dependency which the linter is not checking.
You also can read way more than you expected if you use a `for` loop to read from a socket, or a file, to say nothing about reading from /dev/random.
Not that it's a bad advice, but it's not a rule, it's more like a... guideline. Discretion is still needed.
I can’t imagine making a game (though not sure who makes games in python) without a while loop to run on the main thread.
The ternary operator is mostly a matter of style, and just be aware that for loops is often a better way to iterate through things than while loops. I wouldn't worry too much.