code review

T

Thomas Jollans

This is probably a tautology around here, but *what* *a* *great*
*programming* *language*.

Personally, I don't like this feature of the language. I find a ternary
operator that uses symbols that can also be binary operators confusing
and inconsistent with the way operators usually work/the way terms are
usually associated.

It has the charm of being something you'd "naturally" write in the
context of non-programming mathematics, at the cost of being very odd
indeed in the context of programming in general and Python in particular.
 
A

Alister

If you spell it

def is_valid_password(password):
return mud.minpass <= len(password) <= mud.maxpass

it is even easier to see that you are performing an interval check.

I realise that was possible, that is brilliant! it is exactly how you
would write it ass a mathematical definition.
 
A

Alister

Personally, I don't like this feature of the language. I find a ternary
operator that uses symbols that can also be binary operators confusing
and inconsistent with the way operators usually work/the way terms are
usually associated.

It has the charm of being something you'd "naturally" write in the
context of non-programming mathematics, at the cost of being very odd
indeed in the context of programming in general and Python in
particular.

Surely this fits perfectly with the lines 1 & 7 in the zen of python
(import this)
"Beautifull is better than ugly" and "Readability counts"

I find that construct both beautiful and readable, if it cannot be used
in the languages then that is their loss.
 
T

Thomas Jollans

Surely this fits perfectly with the lines 1 & 7 in the zen of python
(import this)
"Beautifull is better than ugly" and "Readability counts"

I find that construct both beautiful and readable, if it cannot be used
in the languages then that is their loss.

Are we quoting the Zen now?

Contra:

In re usual operator rules:
"Special cases aren't special enough to break the rules."

Which of the two comparisons is done first anyway?
"In the face of ambiguity, refuse the temptation to guess."

Speaking of two comparisons, no "and"!
"Explicit is better than implicit."

Then again, pro:

"Beautiful is better than ugly."
"Readability counts."
"[Although] practicality beats purity."


I can see the appeal. It's quite elegant in and of itself. However, I
find that in the context of the whole Python language, it's a bit of a
glitch and reduces elegance. (I'm probably in the minority on this one)
 
A

Alain Ketterlin

Which of the two comparisons is done first anyway?
"In the face of ambiguity, refuse the temptation to guess."

There is no ambiguity. See the language reference:

"Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN
are comparison operators, then a op1 b op2 c ... y opN z is equivalent
to a op1 b and b op2 c and ... y opN z, except that each expression is
evaluated at most once."

The last restriction (single evaluation of involved expressions) makes
this a bit more than raw syntactic sugar.

-- Alain.
 
T

Thomas Jollans

There is no ambiguity. See the language reference:

Of course it's technically clearly defined, but the syntax isn't
explicit. To know what the order is (or whether there is an order!) one
has to consult the language reference (which shouldn't be necessary), or
make an educated guess, which would almost certainly be correct, but
we're supposed to refuse the temptation to guess, right?
 
T

Terry Reedy

Of course it's technically clearly defined, but the syntax isn't
explicit. To know what the order is (or whether there is an order!) one
has to consult the language reference (which shouldn't be necessary), or
make an educated guess, which would almost certainly be correct, but
we're supposed to refuse the temptation to guess, right?

Python pretty consistently evaluates expressions and equal precedence
operators left to right. One really should learn that. No
'implementation defined' ambiguity.
 
M

Martin P. Hellwig

Surely this fits perfectly with the lines 1 & 7 in the zen of python
(import this)
"Beautifull is better than ugly" and "Readability counts"
Agree, however I like to stress the "don't make me unnecessary read with care" rule. Meaning if I read that line, I have to read it carefully to make sure I understand what is happening, the following would not do that although syntax wise equal:

def length_between_min_max(password):
return(mud.minpass <= len(password) <= mud.maxpass)


def is_valid_password(password):
valid = True

if not length_between_max_min(password):
valid = False

if some_other_test(password):
valid = False


return(valid)

This I can read, typically I would not even read what the function length_beteen_max_min does as long as there is no bug in it because, it is perfectly english clear what the intention is.
 
T

Thomas Jollans

Python pretty consistently evaluates expressions and equal precedence
operators left to right.

Yes. My sole point, really, is that "normally", one would expect these
two expressions to be equivalent:

a < b < c
(a < b) < c

This is clearly not true. That's the inconsistency here with the rest of
the language. As soon as you read it as a ternary operator, the two
comparisons are logically simultaneous. Doing the left hand comparison
first is clearly the intuitive thing to do, but it's still, strictly
speaking, arbitrary. Intuitive, clearly defined, but arbitrary.

The ternary conditional operator is a different beast because its
sub-operators "if" and "else" aren't also binary operators, so it's
obvious that it's parsed differently.
 
A

Alain Ketterlin

Thomas Jollans said:
On 06/30/2012 11:47 PM, Terry Reedy wrote:

Yes. My sole point, really, is that "normally", one would expect these
two expressions to be equivalent:

a < b < c
(a < b) < c

This is clearly not true. That's the inconsistency here with the rest of
the language.

No, comparison operators are different from arithmetic operators in that
they always evaluate to a boolean. There are only rare cases where it
makes sense to compare comparisons.
As soon as you read it as a ternary operator, the two comparisons are
logically simultaneous.

There is no ternary operator, you can chain as many as you want, using
whatever operators:

if a <= b < c > d >= e:
...

Once you view this as a conjonction of conditions, you find back the
semantics of "and": short-circuit, left to right evaluation. I find this
consistent.

-- Alain.
 
C

Chris Angelico

Yes. My sole point, really, is that "normally", one would expect these
two expressions to be equivalent:

a < b < c
(a < b) < c

This is clearly not true.

Python has quite a few things like that, actually. The most noticeable
for C programmers is:

a = b = c = d = e = 0

which in C is identical to

a = (b = (c = (d = (e = 0))))

because assignment is an expression, but in Python is equivalent to
nothing because assignment is simply allowed to do multiple. Downside:
*Only* simple assignment can be chained. Augmented assignment cannot:
File "<stdin>", line 1
a+=b+=10
^
SyntaxError: invalid syntaxFile "<stdin>", line 1
a=b+=10
^
SyntaxError: invalid syntaxFile "<stdin>", line 1
a+=b=10
^
SyntaxError: invalid syntax


In C, these are all well-defined and valid, and are evaluated
right-to-left, and do what you would expect. (And yes, it's handy at
times to do this sort of thing.)

So it's not a special case for the comparison operators; it's a more
general case that Python handles certain chains of operators as single
entities, rather than breaking everything down into "OPERAND OPERATOR
OPERAND" and evaluating one at a time. Is it better than/worse than C?
Not really. It's a design choice and we code within it. (My personal
preference is for the C style, as it makes for a more expressive
language with less mental glitching, but as I say, that's personal
preference.)

ChrisA
 
T

Thomas Jollans

Python has quite a few things like that, actually. The most noticeable
for C programmers is:

a = b = c = d = e = 0

which in C is identical to

a = (b = (c = (d = (e = 0))))

because assignment is an expression, but in Python is equivalent to
nothing because assignment is simply allowed to do multiple. Downside:
*Only* simple assignment can be chained. Augmented assignment cannot:

File "<stdin>", line 1
a+=b+=10
^
SyntaxError: invalid syntax
File "<stdin>", line 1
a=b+=10
^
SyntaxError: invalid syntax
File "<stdin>", line 1
a+=b=10
^
SyntaxError: invalid syntax


In C, these are all well-defined and valid, and are evaluated
right-to-left, and do what you would expect. (And yes, it's handy at
times to do this sort of thing.)

So it's not a special case for the comparison operators; it's a more
general case that Python handles certain chains of operators as single
entities, rather than breaking everything down into "OPERAND OPERATOR
OPERAND" and evaluating one at a time. Is it better than/worse than C?
Not really. It's a design choice and we code within it. (My personal
preference is for the C style, as it makes for a more expressive
language with less mental glitching, but as I say, that's personal
preference.)

Nicely put. Of course it's not catastrophic, it's just a feature of
Python that I'm not particularly fond of.

Another operator with special chaining behaviour that occurred to me is
the tuple-building "," operator. This is a particularly interesting one
since the "," symbol is also used in other contexts where it is not an
operator, and the symbol can be considered an operator in the way it can
be in Python only in very few (if any) other programming languages.
 
C

Chris Angelico

What norm gives you that expectation? That's not how those operators
work in mathematical notation. I know of no programming language that
would give a newcomer to Python that expectation. So where is the norm
you're referring to?

C, SQL, REXX, and many other languages.

ChrisA
 
S

Steven D'Aprano

Yes. My sole point, really, is that "normally", one would expect these
two expressions to be equivalent:

a < b < c
(a < b) < c

Good grief. Why would you expect that?

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

Comparisons are no different. You can't just toss in some arbitrary
brackets and expect the result to be the same. In the second case, the
parentheses explicitly turn the comparison into the equivalent of this:

temporary_flag = a < b
temporary_flag < c

which is not the same as a < b < c.

This has nothing to do with chained comparisons. You can write the same
thing without chaining:

a < b and b < c
(a < b) < c

Is it not obvious that the second case is completely different from the
first? Chaining is irrelevant here.

This is clearly not true. That's the inconsistency here with the rest of
the language.

Nonsense. Your expectation that parentheses should not affect the order
of evaluation is at odds with the entire Python language, to say nothing
of almost every programming language in existence.

As soon as you read it as a ternary operator,

Well that's your problem. Why are you reading it as a ternary operator?
It isn't one. It is a pair of chained binary operator.

How can you tell that it is a pair of binary operators, rather than a
single ternary operator?

1) There are TWO operators, hence a pair, not a single ternary operator;

2) each operator takes TWO arguments, not three.



Chained comparisons is a standard mathematical notation with an obvious
meaning that is familiar to anyone with even a passing familiarity to
mathematics. They have straight-forward and simple semantics. If other
languages don't allow them, so much the worse for other languages.

You seem to be inventing arguments to support an irrational dislike to
the feature, but the arguments don't stand up. By all means say that you
don't like chained comparisons, that is your right, but your attempts to
rationalise that dislike simply do not work.
 
C

Chris Angelico

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)

That's the usual way of depicting order of evaluation: putting in the
implicit parentheses.

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

Everyone who knows algebra knows that the parens are optional, but
nobody would expect them to change the evaluation of the expression.
It's like adding whitespace:

1+2*3 = 1 + 2 * 3 = 1 + 2*3

where the latter is another way of showing order of evaluation (the
asterisk "binds more tightly" than the plus). With comparisons, it's
not the same.

(a<b)<=(c<d)

is, incidentally, a valid expression, as long as you accept that False
is less than True. So if (c<d) is replaced by a boolean variable, you
can conceivably have an expression like:

(len(x)<minimum)<require_minimum

where 'minimum' is an integer and 'require_minimum' is a boolean that,
if set to False, overrides the check. Sure, there's other ways to do
this, but this is at least plausible. And it's completely different
from:

len(x)<minimum<require_minimum

as Python interprets it.

ChrisA
 
R

rusi

Yes. My sole point, really, is that "normally", one would expect these
two expressions to be equivalent:

a < b < c
(a < b) < c

This is clearly not true. That's the inconsistency here....

I dont see the inconsistency with the specific example as you've
given. However if we consider the argument in general, there is
something to be said for being (more) careful to distinguish
associative and conjunctive interpretation of operators. IOW for an
arbitrary operator * (not standard multiply):

If * : t x t -> t, the only meaningful semantics of a*b*c is (a*b)*c
or a*(b*c)
If * : t x t -> Bool the only meaningful semantics of a*b*c is a*b +
b*c
where the most reasonable instance of '+' is 'and'

What happens when t = Bool?

Both cases match. And there is something to be said for notationally
allowing for both cases
Dijkstra/Scholten and David Gries books on logic in computer science
expand on this.

A short net-reachable paper is http://wwwhome.ewi.utwente.nl/~fokkinga/mmf2001a.pdf
 
S

Steven D'Aprano

C, SQL, REXX, and many other languages.

All the worse for those languages, since they violate the semantics of
mathematical notation.

The more I learn about C, the less I want to know about C. What sort of
crazy language designer thought that having

2 == 2 == 2

return 0 (false) was a good idea? At least Pascal gives an error, since
you can't compare bools with longints, and forces you to write:

(2 = 2) and (2 = 2)

Sheer craziness for C to abuse mathematical notation like that. But what
is one to expect from a language where

(unsigned)-1 == -1

apparently is true.

http://nitoprograms.blogspot.com.au/2011/05/signed-and-unsigned-
comparisons-in-c-c.html
 
C

Chris Angelico

All the worse for those languages, since they violate the semantics of
mathematical notation.

Not so. It simply means that booleans are nothing special. In REXX,
there are no data types at all, and "1" and "0" are your booleans. In
C, boolean and comparison operations return integers, either 1 or 0.
Same was true of Python early on, if I understand correctly. It's not
shameful.
The more I learn about C, the less I want to know about C. What sort of
crazy language designer thought that having

2 == 2 == 2

return 0 (false) was a good idea?

It makes perfect sense though; your comparisons simply return
integers, so you can legally index using them. A simple binary tree
can work thus:

node child[2];

next_node = child[search_value>current_value];
Sheer craziness for C to abuse mathematical notation like that. But what
is one to expect from a language where

(unsigned)-1 == -1

apparently is true.

Of course it's true. When you compare an unsigned value to a signed
one, the signed value is automatically up-cast to unsigned, in the
same way that comparing 32-bit and 64-bit integers will do. So
whatever rule the compiler uses to cast your first -1 to unsigned will
be used to cast the second to unsigned, and they'll be equal.

As to "2 == 2 == 2": I don't see it as a problem that:

x = (2 == 2)
y = (x == 2)
z = (2 == 2 == 2)

leave y and z as the same. There are different ways of interpreting
the z statement, but having it identical to y is certainly a plausible
one.

ChrisA
 
R

rusi

All the worse for those languages, since they violate the semantics of
mathematical notation.

The more I learn about C, the less I want to know about C. What sort of
crazy language designer thought that having

2 == 2 == 2

return 0 (false) was a good idea?

Kernighan and Ritchie admit they made a design mistake with their
operator precedences:

http://en.wikipedia.org/wiki/C_(programming_language)#Criticism

That those mistakes are repeated and replicated is more unfortunate.
The second bullet above to be read along with
http://en.wikipedia.org/wiki/Assignment_(computer_science)#Assignment_versus_equality
 
S

Steven D'Aprano

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top