Live data from Hacker News

New Ways to Be Told That Your Python Code Is Bad

nickdrozd.github.io

51–60 of 262 posts

Re: New Ways to Be Told That Your Python Code Is Bad

#51

> 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…

You 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.

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'.

Re: New Ways to Be Told That Your Python Code Is Bad

#52
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?

Re: New Ways to Be Told That Your Python Code Is Bad

#53
post #18
post #3

"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

I 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.

> 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.

Re: New Ways to Be Told That Your Python Code Is Bad

#54
> 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.)

Re: New Ways to Be Told That Your Python Code Is Bad

#55

> 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…

You 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.

> 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).

Re: New Ways to Be Told That Your Python Code Is Bad

#56
post #27

>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?

Even 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…

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.

Re: New Ways to Be Told That Your Python Code Is Bad

#57
post #10
post #7

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…

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"?

I didn't realize this was only for assignments, but the same applies if you prepend `x =` before the relevant lines.

Re: New Ways to Be Told That Your Python Code Is Bad

#58
post #27

>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?

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)

Re: New Ways to Be Told That Your Python Code Is Bad

#59

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". I disapprove of any linter that flags this idiom.

why "while 1:" and not "while true:"?

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.

Re: New Ways to Be Told That Your Python Code Is Bad

#60
post #50

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 disap…

Did you miss the fact that these rules are disabled by default?
Post reply on HN