code review

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Jul 1 00:17:54 EDT 2012


On Sun, 01 Jul 2012 12:20:52 +1000, Chris Angelico wrote:

> On Sun, Jul 1, 2012 at 12:06 PM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> You can't just arbitrarily stick parentheses around parts of
>> expressions and expect the result to remain unchanged. Order of
>> evaluation matters:
>>
>> 2**3**4 != (2**3)**4
> 
> But that's because ** binds right to left. It _is_ valid to say:
> 
> 2**3**4 = 2**(3**4)

Yes, but my point is that you can't just add parentheses arbitrarily 
without understanding what you're doing.

If you're used to some feeble $2 calculator that doesn't implement 
operator precedence, you might expect that you could do this too:

1+2*3
(1+2)*3

But you can't. It's your expectations that are skewed, not Python.

If you (generic you) are used to some feeble $2 language that doesn't 
implement chained comparisons, it is your expectations that are skewed, 
not Python.

Comparisons were invented long before C, or even computers, and they have 
had chained semantics long before Python. Languages like C force you to 
unlearn the standard semantics of comparisons, and substitute something 
that is less expressive, less powerful, less efficient, and more likely 
to contain a bug.

This is almost always wrong in languages without chained comparisons:

a < func(x) < b


This is inefficient, and still wrong, if x has side-effects or isn't 
reentrant:

a < func(x) and func(x) < b


This is too verbose for such a simple comparison:

tmp = func(x)
(a < tmp) and (tmp < b)


[...]
> Everyone who knows algebra knows that the parens are optional, but
> nobody would expect them to change the evaluation of the expression.

Nonsense. Of course parens change the evaluation of the expression. 
That's what parens are for!

There may be some specific cases where they don't, because you have 
happened to put them in a specially crafted position where they don't 
actually change the order of evaluation. E.g. putting parens around the 
entire expression, or putting them around a single atom, or around 
another pair of parentheses:

2*(3+4)
=> (2*(3+4))  # unchanged
=> (2)*(3+4)
=> 2*((3+4))


but just because there are *some* places you can add redundant parens 
doesn't mean that you can add them *anywhere*. You have to understand the 
semantics of expression, including the precedence rules. If you don't 
understand them, you might as well be just adding parens in random 
positions, in which case you should expect the semantics to change.


[...]
> (a<b)<=(c<d)
> 
> is, incidentally, a valid expression, as long as you accept that False
> is less than True. 

Right. I'm not saying that there's never any need to evaluate a 
comparison, and then compare it to the result of another comparison. 
That's a perfectly valid thing to do.

And you know what? You can do it, using parens, exactly as shown.

Chained comparisons is the common case, it should have the simple syntax. 
That's why mathematicians write {x : a < x < b} instead of 
{x: a < x} ∩ {x: x < b}. The uncommon case, treating a bool as a value to 
be compared to another value, should be possible, which it is.



-- 
Steven



More information about the Python-list mailing list