Apart from unexpected behavior it is also about readability: with parentheses in place you can just read left-to-right and the parsing overhead is minimal. Without them though, you have to read almost everything first, parse what is there, figure out what comes first (and hope you remembred it correctly), then read again to get a mental image of what is actually going on.
To be honest I only actually only do this when Im working on old code and find myself trying to remember how something works.
user=> (< 1 2 3)
trueIn APLish languages, it's (a<(b<c)).
julia> 2<3<4
truePython comes second for source code niceness, allowing you to compare numbers to booleans is common and shouldn't be.
c*c:s*s:x*x
with no ambiguity of parsing whatsoever. >>> True == False is False
False
>>> False == True is False
False Python 3.6.9 (default, Apr 18 2020, 01:56:04)
>>> True == False is False
False
>>> (True == False) is False
True
There are worse problems with Python's "is". >>> 1+1 is 2
True
>>> 1000+1000 is 2000
False
This comes from a bad idea borrowed from LISP. Numbers are boxed, and the small integers have boxes built in for them. Larger numbers have boxes dynamically generated. In Python "is" means "in the same box". This corresponds to (eq a b) in LISP.[1] Exposing the implementation like that might have been a good idea when McCarthy came up with it in 1960.[1] http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html
As explained in the link, it's because of chained comparisons, it expands to True == False and False is False. More useful for expressions like 1 < x < 3.
If it must be done for consistency in syntax/parsing, then such combinations should be semantically diagnosed and rejected.
"x is y is z" -> good.
"x == y == z" -> good.
"x == y is z" -> WTF, error.
All the operators in the "relational cluster" should belong to the same equivalence family.
>>> True == False is False
False
>>> True == (False is False)
True
>>> (True == False) is False
TrueAt first glance this looks like an easy problem. But actually building the different parse trees for what I would expect here, and in all cases it should be True.
Rather, there is a more fitting adjective, for which a friendly euphemism can be found: "pythonic".
Implementation equality lets you use an object as a key in a lookup mechanism which adds external associations with an object (which could be de facto properties of that object, or links to other objects). You cannot do that with an equality that deems similar objects to be equal.
eq does not mean "in the same box". In many implementations, it means "same bit pattern". Two unboxed integer objects (fixnums) are eq if they are the same integer (bit pattern). However the ANSI Common Lisp standard doesn't require small integers to be eq to themselves. eql is used for testing for "same number, and eq equality for everything else", which means that 95% of the time you want eql if you think you want eq. The 5% you're sure you're only comparing symbols, or else objects such as structures or CLOS objects for their identity.
In fact, a problem is that it looks like an operator precedence issue at first glance. Of course, TFA explains the answer.
https://en.wikipedia.org/wiki/Tagged_pointer
This issue of object identity and equality isn't unique to Lisp but I agree that applying it to numbers produces unexpected results.
irb(main):020:0> 10.0.__id__
=> 81064793292668930
irb(main):021:0> 10.__id__
=> 21
irb(main):022:0> 10==10.0
=> true
And for very large numbers: irb(main):031:0> 1e200.__id__
=> 340
irb(main):032:0> 1e200.__id__
=> 360
So it's a different threshold and different cases, but as a general rule, "equality and identity are different" still holds.No. IIRC tagged pointers were considered too complex to expose via Python’s C API.
But I can't think of any case with `==` type operators, or really any other operators where it also makes sense.
So was that maybe an overgeneralized feature that should have been limited to the math operators?
I think that’s a pretty reasonable expectation, too.
In JavaScript, {} + [] evaluates to integer 0. That doesn’t make any sense, but it makes more sense after reading the ES spec for the addition operator.
There are many expressions you can write in dynamically typed languages that don’t make any sense, probably most of them actually, but they have to be considered valid because it’s a dynamically typed language. So they’re valid, they will evaluate to something.
The language designers aren’t so concerned with identifying every possible combination that makes no human-intuitive sense. The important part is that when it seems like types should be inferred and coerced in a particular way, then that’s how it should work. It should match human intuition.
I don’t have any intuition or opinion about how True == False is False should be evaluated, this kind of thing is going to receive superfluous parentheses from me every time for the benefit of the reader, and if someone else wrote it this way I’m always going to look it up or test it in a REPL...
10 < x <= 100 though, if that’s considered a valid expression and it doesn’t evaluate to true for numbers in the range (10,100], I’m going to stop using that language...
It's the consequence of ill-thought mechanics when it comes to type coercion. It's the original sin of many scripting languages: "let's just add a bunch of shortcuts everywhere so that it doesn't get in the way of the developer trying to make a quick script". Then you end up with a bunch of ad-hoc type coercion and the language guessing what the user means all over the place, and eventually these bespoke rules interact with each other in weird ways and you end up with "{} + [] = 0".
> I think in language design there’s an expectation that if an expression or statement doesn’t make any sense, then people won’t write it that way.
That's either very idealistic or very naive. In either case I'd argue that's a terrible way to approach language design. I'd argue that many well designed languages don't make any such assumptions.
>but they have to be considered valid because it’s a dynamically typed language.
Nonsense. Try typing `{} + []` in a python REPL. You seem to be suffering from some sort of Javascript-induced Stockholm syndrome, or maybe simply lack of experience in other languages. JS does the thing it does because it was designed(?) that way, not because there's some fundamental rule that says that dynamically typed languages should just do "whatever lol" when evaluating an expression.
Not really - a language could throw an exception in these cases, as Python does for things like “a”+1. Not every dynamically-typed language is JavaScript.
This not really true. Put this js console and you'll see that a is the string "[object Object]":
a = {}+[]
When you put just {}+[] in your console its doing an empty block followed by unary-plus,like: {/*do nothing block*/}; +[] foo == bar == baz
But chaining the == with other operators feels weird, especially "is". Similarly foo >= bar <= baz
feels very off to me, and I'm not sure it should be chainable. If the intuition is to emulate human notation in math, there are many chained expressions that we would not allow to happen.Chaining anything with `is` feels weird to me, but:
foo >= bar == baz >= fooo
fells perfectly ok.
if a <= b > c:
But then, what to do in those disallowed cases? Is the parser powerful enough to make them SyntaxErrors?If not, I’d much rather have the above compiled into the unintuitive `a <= b and b > c` than the completely wrong `(a <= b) > c`.
But, yeah, if there are contradictory comparisons, that should be deprecated and raise an error immediately because it indicates a logic error. If you're doing something awful like using custom operators for side-effects, just write out (a <= b) and (b > c).
Most likely, though, no one is using custom operators for side-effects in chaining because the implicit `and` coerces arguments to booleans. So, with numpy, you can compare two ndarrays with a < b and it returns a new array of booleans, but you can't chain compare three ndarrays because `and` coerces the result to a single boolean.
if x == y == z:(= x y z)
x = y = z = 4;
But I think this is assignment expressions which were controversial in Python or something.I saw two valid ones: "Lossy zip of iterators" is because Python conflates iterables and iterators, "The disappearing variable from outer scope" where deleting the exception is contrary to how scope works everywhere else.
They also miss the classic newbie head-scratcher:
x = []
for i in range(10):
x.append(lambda: i)
x[0]()(a) I don't know Python as well as I thought I did. (b) I suddenly never want to use it again.
All these edge cases! All these behaviors, which I'm sure were added with the noble intention of increasing developer convenience, but which I'm equally sure have cost a larger amount of developer sanity!
It feels to me like javascript in that it's "popular" because people already use/know it. So that huge existing codebase is the equivalent to the web; if you want to build on it, you're stuck with this.
But my lord. Whitespace sensitivity is a terrible choice and there are piles of kludges trying to work around that.
It means no multi-line lambdas so you end up with these unreadable list comprehensions `[ sub_item.value for sub_item in item.sub_items in items if item.is_the_best ]`. Lines copied into the console care about indentation which is definitely not a fun DX.
Not to mention these random global functions everywhere. Whew.
Mistakes were made.
>>> board = [row] * 3
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
I almost felt helpless for an hour when I used this list initialization [0] the first time in my code and couldn't find the reason why my unit tests where failing.
[0] https://github.com/satwikkansal/wtfpython#-a-tic-tac-toe-whe...
I knew about the hash function, and how it generates same hash value for objects that have same numerical value. But it's so easy to forget!
I don't like it either when languages get too helpful. I was recently doing some python and getting some very odd results. Turned out the index I was using to pull stuff from the list had gone negative (off by one error) so it was getting stuff from the end of the list rather than excepting as most languages would. That is
y = [1,2,3]
y[-1] <-- unintentionally negative index
I'm not saying the ability to index with negatives like this is a bad thing in python, but I seriously question it's value vs the footgun thing.A while ago I was doing some SQL and used an answer off stack overflow. It was a good answer but it tried to be too flipping helpful. IIRC it was doing nontrivial date difference calculations[0]. Rather 'helpfully' if "(date2 - date1) < 0" it would kindly assume you'd given it arguments in the wrong order and silently flip them for you (to "date1 - date2") to get you a positive number, always.
This 'helpful' behaviour hid the presence of bad data (date2 should always have been chronologically later than date1 - date1 was interview, date2 was when job started).
Moral: keep programming language & library semantics simple.
[0] I remember now, it was the number of weekdays (that is, excl. sat/sun) between 2 dates.
Just learn the language, python is already one of the easiest languages out there.
They let the programmer take shortcuts, but unless they're always attentive of their implicit behavior, can be a source of subtle bugs.
I say that as a one-time enthusiastic user of CoffeeScript, which felt so refreshing and elegant at first. Over time, especially working with other people's codebases, I came to a personal conclusion that it's not worth the convenience.
Syntax sugar can hide logic that would be better explicit. Even if it feels verbose, there's value in being able to see exactly what's happening.
An example of the latter that I've heard occasionally, is how error handling is done in Go. Every single function call that can return an error must be explicit handled (as far as I know). There could have been some sugar to make it simpler, but they preferred to keep it verbose. That was the right decision, in my opinion, and other aspects of the language give me the impression that it's a consistent design philosophy.
y[end(1)]
y[len - 1]
Either one then constructs a "reverse index" value, and the actual index is determined by the slicing operation.a < b is an expression which gives you a booean value, true or false. Why then are we comparing whether it is less than, or greater than, some number?
Unlike with addition or multiplication:
a < b < c ≠ (a < b) < c
also:
a < b < c ≠ a < (b < c)
instead:
a < b < c = ((a < b) ^ (b < c))
The same criticism does not apply to C's chained assignment expressions, a = b = c, but I dislike that for another reason: if the type of b is a narrower type than that of c, you may get an unexpected value assigned to a.
if lower < x.calculate_weight() < upper:
...
Suddenly turns into x_weight = x.calculate_weight()
if lower < x_weight and x_weight < upper:
... >>> def check_parity(x, expect_odd):
... odds = [ 1, 3, 5 ]
... if x in odds == expect_odd :
... print("ok")
... else:
... print("error")
...
>>> check_parity(5, True)
error
Crazy!"(x in odds) == expect_odd" gives the intended behaviour and I think is easier to read as well.
> if x in odds == expect_odd :
> if x in odds and odds == expect_odd :
with the latter always evaluating to False (assuming expect_odd is a boolean flag).
The 2nd expression is obviously false always.
Though you can skip this lesson if you've worked with Perl (any others?) in the past.
True = False
This is right up there with default arg instances getting cached across calls, though it's perhaps better suited for an underhanded Python competition.
Have fun with it. Redefine it to be true 90% of the time.
I suppose it took awhile for the default Python shipped with systems to be 3.*, because I show people this anytime Gary Bernhardt's "wat" talk is brought up.
Edit :
Here's some of the fun from Python 2.X not treating True and False as keywords:
https://stackoverflow.com/questions/13665989/in-python-how-t...
That is not what is happening at all, logically or semantically. Effectively this is.
# People not understanding when the
# function definition including arguments is evaluated.
mutable_instance = list()
def func(change_me=mutable_instance):
pass def func(change_me=None):
if change_me is None:
change_me = list()
...https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
It doesn't matter what the result is - you know it's going to bite you eventually. If you run a linter on this it would correctly yell at you.
I probably originally had 2 expressions that evaluated to booleans. I may have been using `is` to check that the type of one was actually a bool rather than just falsey.
That isn't surprising I suppose however what is surprising is that I wrote gpython and I had no idea why it worked until I read the explanation on stack overflow about 5 times.
I guess that is the power of having implementing the grammar.
I always like it when my creations (programs or children) exceed me :-)
Part of the issue is that “==“ and “is” are intermixed. That emphasizes the weirdness but detracts from understanding the underlying mechanism that is at work.
If you look at
True == False == False
It makes more a bit sense that it evaluates the way it does.
If you do
1 == 2 == 2
and it evaluates to False, then it is perfectly clear.
It is pretty common that arithmetic comparison operators are grouped to a single precedence level and that's not a problem. But in Python `is`, `is not`, `in` and `not in` are also in that level. In particular two operands of `in` and `not in` have different [1] types unlike others. Mixing them are, either with or without chained operators, almost surely incorrect.
This kind of precedence issue can be solved by introducing non-associative pairs of operators (or precedence levels), something that---unfortunately---I don't see much in common programming languages. Ideally Python's operator precedence table should look like this (compare with the current documentation [2]):
Operator Description
-------------------------------------- ------------------------
... ...
`not x` Boolean NOT
_______________________________________________________________
|
| The following groups do not mix to each other.
| Use parentheses to clarify what you mean.
| ______________________________________________________________
||
|| `in`, `not in` Membership tests
||
|| `is`, `is not` Identity tests
||
|| `<`, `<=`, `>`, `>=`, `!=`, `==` Comparisons
||______________________________________________________________
|_______________________________________________________________
`|` Bitwise OR
... ...
In fact, there is already one non-associative pair in Python: `not` and virtually every operator except boolean operators. It is understandable: the inability to parse `3 + not 4` is marginal but you don't want `3 is not 4` to be parsed as `3 is (not 4)`. My point is that, if we already have such a pair why can't we have more?[1] With an exception of strings (`"a" in "abcdef"`). I hate that Python doesn't have a character type.
[2] https://docs.python.org/3.8/reference/expressions.html#opera...
>>> True is (False is False)
True
>>> True == (False is False)
True >>> (True == False) is False
TrueYeah, that was a real head-scratcher.