attaching names to subexpressions

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sat Oct 27 20:49:19 EDT 2012


On Sat, 27 Oct 2012 15:03:38 +0200, Gelonida N wrote:

> line = complex_exression
> while line:
>      do something with(line)
>      line = complex_expression
> 
> violates clearly the DRY principle  and the risk to change one line but
> not the other is high.

Only if the change is being made by an incompetent programmer.

DRY is not a law of physics. Not every expression needs to be written 
once and exactly once or your code will become an unmaintainable mess. In 
the example shown, the two instances of the expression are separated by 
TWO lines -- if somebody edits one without editing the other, they are 
probably not fit to be editing the code in the first place.

Repeated code is a code-smell, not a crime. You should always be on the 
look-out for ways to minimise repeated code, but that doesn't mean every 
time you see some trivial chunk of code you need to refactor it out.

If "complex_expression" actually is complex, then it should be factored 
out into a function:

line = function(x, y, z)
while line:
     do something with(line)
     line = function(x, y, z)

so that the actual complexity is in one place, and any changes to it 
needs to be made in one place. If it is not actually complex, then who 
cares? So what if you have to write "line = next(iterator).strip()" twice?

If the body of the while loop is big enough that there genuinely is a 
risk that you won't notice the two assignments to line, then you should 
refactor the body of the while loop. Or change the loop:

while True:
     line = function(x, y, z)
     if not line:
         break
     big ugly block of code


There you go. DRY.

There is nothing in this proposal for while assignment that cannot be 
*trivially* solved using existing syntax.


> The 'canonical way'
> while True:
>      line = complex_expression
>      if not line:
>          break
>      do_something_with(line)
> 
> avoids this problem, but I was never really convinced about the beauty /
> readbility of this construct.

It's sure as hell more beautiful and readable than assignment as an 
expression.


> One misses the exit condition of the while loop on the first glance.

If we are going to judge code on the ability of people to take a quick 
glance and immediately understand it, then pretty much nothing but 
trivial one-liners will pass. Certainly assignment as an expression won't:

while (look_ahead(it) and next(it) or default).strip().lower() as line:
    spam(x)

is easy to miss that it is an assignment to x. *Much* easier than missing 
an indented "break".


> In
> my opinion I shouldn't be obliged to read any of the indented lines of
> the while statement on a first 'visual' pass through somebody elses code
> and still be able to see what the loop iterates through.

Fine. Then write your code as:

line = function(x, y, z)
while line:
     do something with(line)
     line = function(x, y, z)


> Following looks in my opinion nicer.
> 
> while complex_expression as line:
>      do_something_with(line)


That's only because you don't actually have a complex expression. Just 
writing "complex_expression" doesn't make it complex -- it's actually 
trivially simple, a mere rebinding of a name to a name, no more "complex" 
than 

while x as y:
    ...

See how easy to read it is! Well duh. But write out an *actual* complex 
expression, and the "as name" can easily disappear into the noise.

Complex expressions are a code smell at least as bad as violating DRY, 
and frequently much worse. If the expression is too complex to write 
twice, refactor it into a function, *and write tests for the function*. 
Otherwise that complex expression is a bug waiting to bite, whether you 
write it once or twice.


-- 
Steven



More information about the Python-list mailing list