20 ms·
New Ways to Be Told That Your Python Code Is Bad
- lmm 5y agoIf expressions are good, but python's hideous middle-endian if expressions are rightfully feared.
- mfgs 5y ago"it unconditionally flags every use of while expressions." I'd find that pretty annoying. Consider a while loop in a thread: while not quit_thread_requested: # do threaded task
- PEJOE 5y agoI agree, but to fair to the author I do not think the vast majority of people are implementing thread pools in python. 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.
- klyrs 5y ago> 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.
- PEJOE 5y agoDid you read the second half of my post? Did you read the authors post at all?
- exporectomy 5y ago> anything that runs for as long as the user says it should run;
- zootboy 5y ago> less code is better than more code 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
- k_sze 5y agoExactly. I pretty much love everything about Python except its ternary operator. I also prefer C’s.
- version_five 5y agoWhen 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.
- deleted 5y ago[deleted]
- mixmastamyk 5y agoArguably? It can be read like a sentence. ;-)
- Dylan16807 5y agoI assume they're talking about the meaning, not how you spell it. So you can read both as a sentence. The arguable part is more about "condition and then thing" versus "thing if condition".
- juanci_to 5y agoExactly. I prefer this a lot more to at least avoid using else: x = 5 if condition(): x = 4
- SkipperCat 5y agoTools 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.
- makeitdouble 5y agoI 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...)
- 29athrowaway 5y agoPython is a sane language that you can master in a reasonable amount of time. If you think expecting people to achieve Python mastery is unreasonable, wait until I tell you about language called C++.
- SkipperCat 5y agoAgreed, 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.
- PeterisP 5y agoI 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.
- l0b0 5y agoSure, 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? if some_complex_condition(using, four, different, parameters): do_a_thing(now, using, five, different, parameters) else: do_another_thing(with, three, parameters) or do_a_thing(now, using, five, different, parameters) if some_complex_condition(using, four, different, parameters) else do_another_thing(having, three, parameters) (or do_a_thing(now, using, five, different, parameters) if some_complex_condition( using, four, different, parameters ) else do_another_thing(having, three, parameters) after Black.)
- detaro 5y agoNeither 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"?
- l0b0 5y agoI didn't realize this was only for assignments, but the same applies if you prepend `x =` before the relevant lines.
- balthasar 5y agoI would write it like this. Seems fine to me do_a_thing(now, using, five, different, parameters ) if some_complex_condition(using, four, different, parameters ) else do_another_thing(having, three, parameters)
- nirs 5y agoTheses are good examples for not using ternary expressions. The code looks wrong even if it is correct, and it is hard to follow. There is a very simple rule avoiding most of these issues - if the ternary expression does not fit in one line, use if/else.
- ChrisMarshallNY 5y agoI love linters. 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 https://github.com/realm/SwiftLint I write about it here: https://littlegreenviper.com/miscellany/swiftwater/swiftlint/ https://littlegreenviper.com/miscellany/swiftwater/swiftlint...
- 29athrowaway 5y agoHating 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.
- Zababa 5y agoLinters 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.
- 29athrowaway 5y agoThat's a straw man argument.
- Zababa 5y agoThat'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.
- throwaway81523 5y agohttps://xkcd.com/1833/ https://xkcd.com/1833/ has some good ones.
- chriswarbo 5y ago> 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).
- joshuamorton 5y agoYou don't need the walrus operator, as keyword args are valid. f(x=a if b else c) Should be valid. All the walrus gets you is making the x "infect" the surrounding namespace which is surprising.
- hermitdev 5y agoExcept 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'.
- chriswarbo 5y ago> 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).
- jimmaswell 5y agoI have a much better time with Python just doing what I want instead of following the Python community's sometimes arbitrary and capricious standards.
- Spivak 5y agoI 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.
- dimitrios1 5y agoGod bless gofmt and tools like prettier.
- maleldil 5y agoPython has Black, which uses the same premise as gofmt. Guido doesn't like it, though, but the community seems to.
- dimitrios1 5y agoWhich 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.
- BiteCode_dev 5y agoThe 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.
- jimmaswell 5y ago
- scythe 5y ago>most code is not complex enough to warrant while loops, scale = 5 while (scale > 0.1): do_stuff(scale) scale = scale/2 Sure, you could use exponentials with range(), but is that really clearer?
- chriswarbo 5y agoEven clearer: 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)
- markrages 5y agoPoe's law demonstrated.
- Chris_Newton 5y agoIndeed, 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: iterate (/2) 5.0 & takeWhile (>0.1) & map do_stuff 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.
- priansh 5y agoI 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.
- H8crilA 5y agoThe 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)
- tbenst 5y agoI think “while loops” are orthogonal to primitive recursive functions.. for example, here’s how to compute the Ackerman function using only recursion in several languages; https://stackoverflow.com/questions/16115815/ackermann-very-inefficient-with-haskell-ghc https://stackoverflow.com/questions/16115815/ackermann-very-...
- H8crilA 5y agoIt 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.
- notatoad 5y agoi 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-control.html 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)
- mst 5y agoI 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.
- 29athrowaway 5y ago> 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.
- markrages 5y agoPython 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". I disapprove of any linter that flags this idiom.
- tempest_ 5y agowhy "while 1:" and not "while true:"?
- markrages 5y agoI think that's brain damage from years of embedded C.
- hermitdev 5y agoBecause "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.
- chrismorgan 5y ago> 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.
- fomine3 5y agoEnjoy YAML with Norway. https://news.ycombinator.com/item?id=17359376 https://news.ycombinator.com/item?id=17359376
- priansh 5y agoWhile 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.
- frosted-flakes 5y ago
- QuadrupleA 5y agoSo is the author suggesting that a while loop like this: while not quitRequested: processNextEvent() Should be rewritten to this? for i in range(999999): if quitRequested: break processNextEvent() Because that's supposedly guaranteed to halt? If so he's either joking or crazy. And his program will crash for his poor users after 999999 events.
- tutuca 5y agoI guess `quitRequested` falls in: > anything that runs for as long as the user says it should run; Per the article...
- QuadrupleA 5y agoWell his linter flags every occurrence, apparently. Because "that’s the fact of the matter, and you need to come to grips with it."
- detaro 5y agoIf 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). Probably not for me though.
- chrismorgan 5y agoIn 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.
- Zababa 5y agoI 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?
- q-rews 5y ago> 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.
- Zababa 5y ago> 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.
- chrismorgan 5y ago> A while loop introduces unbounded computation. 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.)
- phendrenad2 5y ago> A while loop introduces unbounded computation Someone needs to inform this guy that generators exist
- yongjik 5y ago> 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.)
- mhh__ 5y agoThat'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. If you're in charge, that is...
- yongjik 5y agoWell, 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).
- q-rews 5y agoCongrats on insulting someone you don’t know because he made a valid point without saying anything about the point they made. They love linters, you sound like you hate linters. Don’t use them and let your colleagues deal with your unbounded computation.
- deleted 5y ago[deleted]
- nine_k 5y agoWhile 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.
- choeger 5y agoYou 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.
- uryga 5y ago>>> x = [0] >>> for x in xs: ... print(x) ... xs.append(x+1)
- toxik 5y agoOr 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. )
- lxe 5y agoThere shouldn't be so many ways to do loops. Assembly got it right the first time.
- dijit 5y agoUnbounded 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.
- flor1s 5y agoRight, luckily it's not enabled by default. Some other fields in which unbounded computation is common are IOT and robotics.
- deleted 5y ago[deleted]
- kristaps 5y agoThe 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.
- greenie_beans 5y agoI’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?
- hnfong 5y agoMy 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.
- lelanthran 5y ago> Python programmers in general have an irrational aversion to if-expressions If that's the case, maybe the problem is the if-expression, and not the programmers? If you have a syntactical construct in your language that the programmers are avoiding in general, then that construct has problems.
- empiko 5y agoPeople raging over the ternary operation in Python should learn to use: x = {True: 5, False: 4}[condition()] , or if you don't want to evaluate the values: x = {True: lambda: 5, False: lambda: 4}[condition()]()
- deleted 5y ago[deleted]
- jusssi 5y agoThis pattern also nicely expands when you need to select from more than 2 values. And it lets you keep the mapping as a data instead of turning it into logic. {"a": 1, "b": 2}.get(selector(), default_value) for the else-case.
- vsnf 5y agoThis is perhaps one of the most obscure ways to do ternaries I could have thought of, and while it really is quite clever, I think I'd be pretty annoyed if I encountered it in the wild.
- boomlinde 5y agox = (4, 5)[condition()] for a bonus "what am I looking at again?"
- Sinidir 5y agoBrilliant.
- aimor 5y agoI just want to read and write: x = if condition(): 4 else: 5
- reportgunner 5y agoHow is this new ? Perhaps it's new for the author. Python one liner ifs were not introduced yesterday.
- apocolyps6 5y agoMost of the time when I see python ternary in the wild, it makes the code less readable, not more. Why not just define something like this if you want oneliners? def if_then_else(cond, true_case, false_case): if cond: return true_case return false_case x = if_then_else(condition, 4, 5)
- shultays 5y agoConsider this: x = if_then_else(condition, calc1(), calc2()) Here it will call both calc1 and calc2 even though it will ignore one depending on condition. They might be expensive operations, or might have side effects that you don't want. The original code does not evaluate the part that is not used
- apocolyps6 5y agoIn that case I'd stick an `if callable` check in if_then_else. But really... ternary is a nice way to draw attention away from simple pieces of code. If you have anything complicated (like functions with side effects) spend the extra 3-4 lines to make it readable
- ohazi 5y ago> Python programmers in general have an irrational aversion to if-expressions, a.k.a. the “ternary” operator. Because the Python ternary operator is fucking backwards! Why on earth would you put the condition in the middle!? x = 4 if condition() else 5 vs.: condition() ? 4 : 5 vs. the author's own lisp example: (setq x (if (condition) 4 5)) I love ternary operators, and Lisp/ML-style if else blocks that return things, but Python puts the 4 and the 5 so far away from each other that even I hate using it, especially when condition is a chained mess rather than just a function call.
- chakkepolja 5y agoIn practice it's not that bad. One bad thing is you can't write long chains as you can do with C-style ternary operator.
- Cthulhu_ 5y agoThat's not a bad thing, long ternary chains are illegible. You should never have more than one condition in a ternary.
- pdpi 5y agoThere are some specific shapes of chained ?: that are fairly legible. Eg c = condA ? valA : condB ? valB : defaultVal;
- DonHopkins 5y agoFor Turing's sake, if you insist on chaining ternary operators, then you should always use parens instead of showboating that you cleverly-by-half memorized a particular language's obscure precedence and associativity rules. PHP in all its glory and splendor actually originally used left-to-right instead of right-to-left associativity for the ternary ?: operator, so that expression you wrote works differently in PHP and (JavaScript or C or any other language). (Side note: notice how I used parens instead of depending on you guessing about the relative precedence of "and" and "or" in English.) It's easy (and perfectly valid) to blame the designers of PHP for not knowing or caring or paying much attention to the syntax of the programming languages they were imitating, and doing something that stunningly stupid, but it just goes to prove that many people (especially PHP designers like Rasmus "I don't care about this crap at all" Lerdorf and typical PHP programmers) don't even have the faintest idea what the precedence and associativity of many of their favorite programming language's operators are, or what those terms even mean, and they just willy nilly cargo cult copy and paste and translate between PHP and JavaScript code examples from Stackoverflow, introducing terrible hard-to-spot bugs. https://en.wikiquote.org/wiki/Rasmus_Lerdorf https://en.wikiquote.org/wiki/Rasmus_Lerdorf >"We have things like protected properties. We have abstract methods. We have all this stuff that your computer science teacher told you you should be using. I don't care about this crap at all." -Rasmus Lerdorf But the real fault lies not just with PHP for parroting C incorrectly, but with C for doing something that stupid and hard to remember and easy to get wrong in the first place. Please always use parens for that kind of stuff, because even if YOU memorized and love to depend on and show off your knowledge of the precedence and associativity rules of the language you're using, the people reading and modifying and maintaining your code probably don't. https://wiki.php.net/rfc/ternary_associativity https://wiki.php.net/rfc/ternary_associativity >PHP RFC: Deprecate left-associative ternary operator >Unlike most (all?) other languages, the ternary operator in PHP is left-associative rather than right-associative. The left-associative behavior is generally not useful and confusing for programmers who switch between different languages. This RFC proposes to deprecate and remove left-associativity for the ternary operator and require explicit use of parentheses instead. >As an example, the code return $a == 1 ? 'one' : $a == 2 ? 'two' : $a == 3 ? 'three' : $a == 4 ? 'four' : 'other'; >would in most (all?) other languages be interpreted as return $a == 1 ? 'one' : ($a == 2 ? 'two' : ($a == 3 ? 'three' : ($a == 4 ? 'four' : 'other'))); >which is both the useful and intuitive interpretation. In PHP, it is instead interpreted as return ((($a == 1 ? 'one' : $a == 2) ? 'two' : $a == 3) ? 'three' : $a == 4) ? 'four' : 'other'; >which is generally not what was intended. https://stackoverflow.com/questions/20559150/ternary-operator-left-associativity https://stackoverflow.com/questions/20559150/ternary-operato... >Q: Can someone please explain what is happening here and why it is printing 'four'? >A: Because your whole expression evaluates as if it was (......) ? 'four' : 'other'. Since the first element is probably something truthy, it gives you 'four'. In saner languages, where ?: has right associativity, the whole expression evaluates as if it was $a == 1 ? 'one' : (......), where if $a is not 1, you go on to test other things. http://phpsadness.com/sad/30 http://phpsadness.com/sad/30 >PHP Sadness: Ternary operator associativity >The ternary operator is left-associative and therefore behaves entirely incorrectly: https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/ https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/ >PHP: a fractal of bad design >Unlike (literally!) every other language with a similar operator, ?: is left associative.
- dragonwriter 5y ago> But here’s the reality: most code is not complex enough to warrant while loops, and most while loops are better written as for loops. Hard disagree. Obviously, if the loop is a fixed number of iterations or iteration over a collection (which is trivial to identify by human inspection but probably not for a linter), a for loop is better but it is not uncommon to be looping until a (possibly compound) condition becomes true that cannot be expressed as a fixed number of iterations or (at least naturally; you can embed arbitrary complexity into a generator, though usually, absent unbounded recursion, that will just push the “while” down behind a layer of abstraction) as exhaustion of an iterable.
- joatmon-snoo 5y agoI've actually found that there are a number of situations where I really want: while True: ... if condition: break ... and kinda wish there was a nicer syntax for this.
- ohazi 5y agoRust has `loop {}` which I thought was weird at first, but also ended up use more than I would have expected.
- kevinmgranger 5y agoOne especially nice part of rust's `loop` is since it's guaranteed to run at least once and to terminate, you can break with a value! let foo = loop { // ... if condition { break value; } // ... }
- dragonwriter 5y agoA Ruby-like “until” or Pascal “repeat...until” (C’s do...while is equivalent but less clear choice of keywords, IMO.) The Pascal form wouldn't really fit Python's indentation-without-closing-marker block syntax preference, but the Ruby version would. If it existed, I’d be tempted to suggest a linter rule that prohibits break inside an until (it should only be used when the main exit condition is the only exit condition.)
- _ZeD_ 5y agoI have problem with both new checkers: regarding the `consider-ternary-expression` if condition(): x = 4 else: x = 5 allow me to put fck'n breakpoints - in this case the expression is dumb, but what if is some complex stuff? x = 4 if condition() else 5 is cute, but - remember, kids - not everything* should be an expression regarding the `while-used`, while I personally prefer the legibility of the `for` loop, I find the following paragraph... at best wrong: >>> A while loop introduces unbounded computation. Do you really need unbounded computation for your boring web app? I doubt it. In almost all cases, the loop can be bounded in advance, as in “do this N times” or “do this for every item in this list”. Those kinds of loops are guaranteed to terminate, and that’s a nice guarantee to have. in python `for` loop are equally unbounded as `while` ones. Yes, most of the time you are looping over a (finite) list or a (finite) dict, but the concept underlying the `for` loop is the iterator, not the list. And iterators can easily be infinite.
- ghgr 5y ago> in python `for` loop are equally unbounded as `while` ones. Yes, most of the time you are looping over a (finite) list or a (finite) dict, but the concept underlying the `for` loop is the iterator, not the list. And iterators can easily be infinite. Not only iterators, but also plain lists. Example: a=[1] for i in a: a.append("the ride never ends") print(i)
- MathMonkeyMan 5y agoAh, leave people alone. I stopped using ternary expressions because someone told me not to. I could start using them again, whatever. I use while loops when the burden of describing the loop as an iteration is too high (too much of a stretch), or when I'm writing something that really isn't a composition of forEach/map/filter/reduce (gasp). For me, it's whatever I need to do to get through code review without arguing too much. Don't add linter checks for these things, it's condescending. At the very least, have the "greater good" on your side, e.g. "it confuses some people!" or "it is often a bug!" or "it can become a maintenance nightmare!" or whatever. If your reason is "I like it better this way," well then yeah, the reaction I would anticipate is "who asked you?" The reason this bothers me isn't that I strongly disagree, but that I'm in the habit of doing whatever my linter tells me, and adding stuff like this cheapens the thing and makes it harder to sell to "I'm an expert, get off my lawn" people. Why am I even writing this, I'm knee deep in C++ these days.
- heresie-dabord 5y ago> For me, it's whatever I need to do to get through code review without arguing too much. The language quibblers of the programming industry have installed a set of opinions, not knowledge. They proceed to waste time and energy with their assertions.
- musingsole 5y ago> The language quibblers of the programming industry have installed a set of opinions Pray they don't install another one.
- tbronchain 5y agoThis, really. I'm most likely less experiment than some people here but after 10+ years of writing code, I came to the conclusion that the best code is the most readable code. If all developers could write code that is easily readable for them (even after a few months break in the project), the software engineering world would be a lot better :) Of course it makes sense to avoid anti-patterns and bottlenecks, but no much more really.
- loganlinn 5y agoSomething tells me the author would appreciate the minutia of working in functional languages that tend to avoid entire classes of problems like those described and the dogma used to fight it.
- jbverschoor 5y agoI have more problems with: - extensive small functions/methods - people who don’t use early escapes
- deleted 5y ago[deleted]
- maleldil 5y agoWhat's the problem with small functions?
- fho 5y agoSome (including me) would argue that writing any loop yourself is considered harmful and you should be better of using higher order functions. In Haskell there is a higher order function for basically every usecase and (most of the time) the compiler is smart enough to merge chained higher order functions into a single loop. Python is at least trying with its `itertools` package, but it's still a far cry from the generalization "modern" languages allow. (Background: I was fortunate enough to program in Haskell for 2 years. It was great, but I can see that it's basically useless for a lot of common usecases :-( )
- Joker_vD 5y agoOkay, so how should the following loop be rewritten: while self.keep_going: s = input('> ').lstrip() if s == '': continue cmd = self.parse_cmd(s) # The "quit" commands sets self.keep_going to False, oh the horrors # of the global mutable state cmd() ? Should it be: for cmd in self.stream_of_commands(): cmd() But stream_of_commands() will still have a loop inside it, wouldn't it? Because that's how you write generators?
- clownpenis_fart 5y agoah yes, surely it is possible to restrict yourself to a subset of the language that is guaranteed to terminate and is therefore not turing complete and still be able to implement anything you want
- gigatexal 5y agoi dunno re: the while loop -- a while loop with a condition that breaks the loop is perfectly fine by me.
- tpoacher 5y agoWell, he's right that it's opinionated. If I were to contribute to that project I'd opinionatedly implement the exact opposite linter, suggesting proper if blocks instead of a ternary expression. The reason is simple: visual code structure. A bespoke if block provides you with appropriate visual semantics that that part of the code involves a decision point. By contrast, the ternary operator is useful when you want to visually obfuscate that fact, and make the code terser and more focused on the fact that an assignment is taking place, whose value is determined by an expression (which upon closer inspection happens to be conditional). Yes I know that in the age of super-fat IDEs everything is but a search away, but, I've just come off from reading that other article from today about the guy who handwrites all his code. So clearly low-level visual structure and ease of mental debugging still matters, no matter how bloated and feature-full your IDE is. But what do I know. I still code on vim/nano.
- wirthjason 5y agoThis! When reading/skimming through source code I find the structure very important to understand that a decision is being made and not just a simple variable assignment. Sometimes more code is easier to read than shorter code. Even
- w_t_payne 5y agoLinters are good because they stop people arguing. Some early employee in the team defines some set of rules, and that's just the way things are from then onwards. Less time bickering means more time spent doing actual productive stuff.
- chrismorgan 5y agoLinters are bad because they force your code into unnatural shapes, preventing you from exposing visual patterns and harming code comprehension, and also regularly harming diff sizes. (Both of these statements can be true.)
- w_t_payne 5y agoTrue. Both can be true! :-p
- dethos 5y agoIs this a joke, right? Do you really need unbounded computation for your boring web app? Because everyone using python is working on web application. Also because all web applications have the same type of business logic. I do like linters, the best ones help avoiding mistakes and ensure some consistency in the codebase, but this article goes a bit too far.
- cafard 5y agoIn one of Raymond Hettinger's YouTube presentations, he speaks slightingly of pylint. These days I use flake8, but not pylint.
- arusahni 5y agoFWIW, ternary expressions aren't properly detected by coverage: https://github.com/nedbat/coveragepy/issues/509 https://github.com/nedbat/coveragepy/issues/509
- rubyist5eva 5y agoI don’t agree with these opinionated defaults, but I’m primarily a Ruby programmer so my tolerance for expressive code is higher. As long as the linter is configurable I don’t really care.
- syats 5y agoI always had the vague impression that the original purpose of linters was for some arrogant ahole to impose his "style" on other people. Now I have confirmed this impression.
- rogual 5y agoA lot of bloggers love linters, and they will look for any reason to promote them. They will say things like “programmers don’t like to have their code criticized” and “chicken-little reaction” and other excuses. But fundamentally the problem is that bloggers like to tell others what to do. Well, tough shit, because I closed the tab.
- MisterTea 5y agoThose who can, do. Those who can't, teach. Those who can't teach, write blog posts.
- WesolyKubeczek 5y agoInspired by the boldness of this article, I decided to make my own linter which was going to be the most opinionated of them all. Since my gut feeling is that 99% of the code is always shit that should not have been written at all, I have coded a linter that simply flags every line of your code and strongly advises you to delete it. And also change your trade if you can. This linter was a very good invention. But then I got caught up in the spirit of being fair and “eating my own dog food”, and ran the ultimate linter on its own code. It told me the code was pathetic shit and that I should delete it, and never write a line of code again because very likely it was going to be same shit, but worse. I followed its advice.
- devney 5y agoI just feel like this author could have chosen a better example. The if/else block that he complains about is easy to read and simple to understand. The ternary he advises raises the bar significantly for anyone trying to read the code, while having no practical effect on runtime or usability. Based on my 25+ years of experience I strongly believe that the "if/else" that this author complains about is actually BETTER than the ternary he recommends. This is yet another example of the endemic problem where software engineers think "harder to read is a virtue".
- mannykannot 5y agoClaiming that the 'too many false positives' objection is just an excuse for not liking having one's code criticized is itself just an excuse for dismissing a real problem with linters. When writing code from scratch, I am generally eager to use static checking, but when I am flooded with complaints about things I cannot reasonably do anything about, it becomes useless. ...And I wrote 'generally eager', because having a linter raise pedantic concerns over trivial matters is a way to extend this problem across from-scratch code as well.
- deleted 5y ago[deleted]