Why does `True == False is False` evaluate to False in Python? (2013)
stackoverflow.com
stackoverflow.com
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.
Honestly, Python's way is the only sane way to parse this. This is how a human who never programmed before with some knowledge of math would understand it.
Everyone's else mind is just corrupted by broken legacy languages.
>>> a=16
>>> a*a is a*a
True
>>> a=17
>>> a*a is a*a
False
“is”, “==“, and “=“ are all very different and none of them match their mathematical equivalents very well.The objects for small integers are built-in and static, but 17^2 is too big, and so new integer objects are created when the expressions are evaluated. "is" checks if two expressions represent the same object in memory. "==" checks for equality of values.
if a == 2 or 3:
do stuff
because that's how they would write it in math. Does that mean Python is broken and should support that construct as well? # See stackoverflow/highlyupvoted/wtf/does/python/do/here
# This is the same as (a < b) < c.
x = a < b < c
whih is not clearer, not easier to parse and not faster to read and harder to maintain (leads to rot easily when variable names change) than the non-ambiguous other choice.In APLish languages, it's (a<(b<c)).
Python comes second for source code niceness, allowing you to compare numbers to booleans is common and shouldn't be.
In most languages, like C++, a < b < c ends up comparing a boolean to an integer. This should be a type error, but isn't.
user=> (< 1 2 3)
trueI have programmed python for about 20 years, and I still do stupid mistakes. I grooked all the r6rs scheme syntax in 20 minutes, except for (do ...) which somehow never sticks. It is also rarely used.
I have tried to evangelize scheme enough to know some people just don't like it, but for me it instantly clicked.
The problem I think is that not= returns true as long as any elements are non-equal, which means it is not analogous to +. I can't speak for clojure, but this is in line with all equality predicates in scheme, negated or not. A predicate that checked if any neighbours are not equal would in true scheme spirit probably be called not-any-neighbour-equal? :)
Parentheses take precedence.
The first symbol is the operator (add, subtract, sum, multiply, etc.).
Everything else is operated on from left to right.
(+ 2 5 7)
2+5+7
It's also not a Clojure thing. It applies to all Lisps or related like Scheme.
julia> 2<3<4
true>>> x = 25 * 37
>>> y = 25 * 37
>>> x is y
False
>>> x == y
True
From my understanding, '==' checks for equality and 'is' checks that the variables reference the same address.
>>> a=16
>>> a*a is a*a
True
>>> a=17
>>> a*a is a*a
False
WTFSee https://stackoverflow.com/questions/306313/is-operator-behav... for some discussion and further related links.
Otherwise you'll end up thinking that almost everything is simply an alias for constant True or 0 or Error, by popping in 0 to any operator you check.
At the CPython level, you can explain it in terms of the particular range of small integers that are interned and thus are guaranteed within particular CPython versions to share object identity when they have value equality.
But just knowing that is identity and == is equality is mostly enough to use them correctly.
>>> True == False is False
False
>>> False == True is False
FalseOr read the very first part of the linked page that shows:
>>> (True == False) is False
True
>>> True == (False is False)
TrueYou'd be correct to use 'is' to mean 'is', and '==' to mean '==', following their definitions.
Other than that, just don't use `is`.
To be honest I only actually only do this when Im working on old code and find myself trying to remember how something works.
I liked Go and used to work on it in my personal time. Go's way of doing things is usually one way so there are not many things you have to remember about language. I applied for a job of Go and I landed that one. Of course, at that time very few Go developers were there in market so that could be a good reason for me landing that job as well.
I'm glad I never had to go through such ridiculous hoops to get a job.
If I had I might have been inclined to say "Yes I could but I wouldn't do it because it would be unmaintainable."
Real application programming isn't about being concise it's about being cost effective. Sometimes conciseness achieves that end often it does not. Having a well named intermediate variable can help document what is happening so that the poor sod who has to fix a bug in or extend your code two years later doesn't have to spend an hour deciphering your code to apply a two minute fix. That poor sod might be the very same person who wrote it.
num%3 == 0 ? num%5 == 0 ? "fizzbuzz" : "fizz" : num%5 == 0 ? "buzz" : num
Most formulas students need to learn are actually quite intuitive when you replace the symbol with a one or two word description. But they'd rather force you to memorize the symbol.
It's like forcing you to read obfuscated code. I find it draining with zero benefit to most learners.
Can you give some examples of this?
Yes, some foundations of facts are needed. For example, there's some very basic operator precedence that students should internalize. And history inevitably does involve names and dates. But too much attention is probably devoted to remembering whether a battle was fought in 1746 or 1750.
It's somewhat understandable because testing for those things is easy and unambiguous. But it's unfortunate anyway.
I think Latin is still used as part of the traditional service, but it's mostly ritualistic. What I remember from attending Catholic services years ago is a lot of standing up and sitting down while chanting in Latin, followed by a sermon delivered in English.
Reducing math to words means English math is different from Chinese math. If I, a native English speaker, were to look at such math, I would have to more or less take it on faith that the translation I'm reading is accurate. And god forbid I'm trying to read a translation of a work done jointly by (say) a Romanian and a Chinese.
The symbols generally remove ambiguity. Consider the English word "bi-weekly." It's so muddied that it is almost useless. It means either twice a week or once every two weeks. It means, literally, both multiplication and division; and context is rarely helpful for this particular word.
You can certainly use the symbols poorly and ambiguously, as many viral "Solve this math problem!" memes exemplify. But used properly they're generally clear, concise, and well-defined.
I do, however, have one nitpick about symbols, and that's with the use of the ellipsis in math. You'll often see things like {1, 2, ..., n} which is generally intended to mean (say) the natural numbers up and including to n. But does it? Couldn't it also represent powers of 2? Or the set of fibonacci numbers? And since it is a set, order doesn't matter. Almost anything at all could be in that set. We can only be sure it's not something like "all the odd numbers" because 2 is in the set. Frustratingly, it's not even necessary in most cases, since we have set-builder notation and other tools.
I'm not saying that math shouldn't be using symbols, I'm saying it's near intentionally hostile to those first picking up the subject. If the goal of math education is to provide the groundwork of understanding to the general populace... Cater to the general populace. The vast majority of those folks are not going to be discussing complex math across several languages.
They're going to be using it for accounting, taxes, construction, cooking, etc. They don't need to memorize an entire language to do that effectively, and we shouldn't be wasting their time in school trying to make them.
Those that choose to specialize are welcome to. In my opinion that doesn't justify the use of specialized language in basic education settings.
I do have criticism with symbols insofar as math is taught which roughly fall in line with "A Mathematician's Lament" by Paul Lockhart[0]. Loosely, that much math is taught as symbol manipulation rather than... actual math. But that's not a problem with the symbols themselves so much as with their use to obfuscate what's actually being done mathematically.
You might be interested in the book Burn Math Class by Jason Wilkes [1]. I don't really recall what his arguments against symbols were. He ends up inventing his own notation as he goes along (which is what ultimately turned me off the book about halfway through -- it just became an exercise in translating notation for me).
[0] - https://www.maa.org/external_archive/devlin/LockhartsLament....
[1] - https://www.amazon.com/Burn-Math-Class-Reinvent-Mathematics-...
Edit: Fixed list of links.
It's easy to fall into a trap where you forget what it's like to be completely new to a subject. It's particularly hard when you tend to surround yourself in educated communities where this "in-knowledge" becomes assumed and standard (eg hacker news).
Take just less than ( < ) and greater than ( > ). Think about how many stupid rhymes or memorization techniques you see in classrooms to help learners memorize which is which.
Here are three separate sites, with an entire page dedicated to helping students remember which is which:
https://math.wonderhowto.com/how-to/remember-greater-than-le...
https://numberock.com/lessons/comparing-numbers-to-100/
https://myhomeworkdone.com/blog/greater-than-less-than-sign/
One of them includes a whole damned song for the purpose. All to avoid writing out smaller/bigger.
Like any language, once you learn it's hard to remember all the places you struggled.
Why make "change" Δ
Why make "square root of -1" i
In how many classes do we see rote memorization of the quadratic formula, with no context around why you should even bother to learn it? (I've seen quite a few).
Now, not all of those are really the fault of the language (math), but using the language for each of those problems facilitates lazy teaching, and it changes the goal from "understand how math relates to the world" to "memorize this language construct". One is much more helpful than the other.
It makes sense that specific knowledge uses specific language to be more easily used and manipulated. Imagine writing (or even proving) Euler's equation without using i or pi or e. Imagine what mess math would be if we never used Greek symbols.
Sure, it would be easier for middle schoolers. But if you're arguing that we should make it easier for them since they're not gonna need ease of manipulation for math that they're not gonna use... Then just do the teach it to them. Maybe middle schoolers don't need < as a concept any more than they need it as a symbol.
But if you're going to solve equations and inequalities, then yeah, you need = and <. Everything else would be needlessly verbose and get in the way of actually manipulating concepts you know.
As for younger students, I have much less experience, but some; and it's interesting you mentioned less than and greater than; since I actually remember learning those symbols. We learned that the "alligator" always eats the "bigger" number. It's not surprising there are countless ways of learning it, including song. That's true of almost any abstract concept. The idea is to link a metaphor the person understands to the abstract concept. Not every metaphor will work for every person; and this is true of all abstract concepts, not just math symbols.
Of course, it's not actually true that the alligator is eating the "bigger" number, and it actually demonstrates why we need the symbols. ">" and "<" actually refer to "greater than" or "less than" which we much later learned is a way of saying "which number is further right on the number line"; which, of course, requires the abstract concept of the number line and accepting the more-or-less arbitrary decision of a left-to-right number line. "Bigger" means "has a greater distance from zero on the number line in either direction" which we'd represent as a comparison of absolute values. I don't recall when I learned about absolute values, but it was definitely years after learning about < and >. Using the proper symbols lets us be explicit, concise, and precise and avoid issues like using English synonyms (bigger, greater) or whatever pitfalls exist in other languages.
The choice of symbols < and > are, of course, largely arbitrary other than the symmetry between them. (We could have, for instance, always put the greater number underneath the smaller number so the structure is more stable in an imaginary gravity; but that, too, would be arbitrary.) But so is the letter S, or the number 9. They're all arbitrary symbols that have particular meanings in particular languages. "9" is interesting, because it's a number, versus the Roman numeral system. The Roman numeral system could arguably be called non-arbitrary. "I" clearly represents a single thing, "II", two things, etc. That works until you get up to "IV". What? "IV"? So if a lesser value is in front of a greater value, you subtract it? And how does "V" represent five anyway? It's arbitrary! But the Romans found it much more useful to be able to write VII + VI = XIII rather than IIIIIII + IIIIII = IIIIIIIIIIIII, which can pretty quickly get unruly. Turns out, memorizing digits 0-9 makes it (and more complex math) even easier: 7 + 6 = 13; which is why the entire world uses numbers instead of numerals.
(We could also have a side-discussion on why base 10 and not something like base 12, binary, a mixed radix system that uses the prime numbers or the sexagesimal system used by the Sumerians. The answer is basically that its mostly arbitrary, simpler than some systems, and we have ten fingers/thumbs.)
> Why make "change" Δ
> Why make "square root of -1" i
Largely historical reasons, expediency, and lack of better alternatives. Why represent the sound "ssss" with the symbol "s"?
You could swap i for √-1 and people would understand you, but you'd very quickly wish there was a shorthand that you could use to represent this rather special value.
> In how many classes do we see rote memorization of the quadratic formula, with no context around why you should even bother to learn it? (I've seen quite a few).
You won't see me objecting to this; but this is not an issue with mathematics. It is an issue with teaching mathematics and is part of the "lament" I linked to above. This is quite a different issue than the issue of symbols. The symbols, while arbitrary and arcane, actually make the mathematics more manageable and precise. Saying that "facilitates laziness" is like saying a clothes washer facilitate laziness since it removes the need to manually provide friction and agitation. It's true, in a sense, but I'll keep my washer.
Mathematics is taught very poorly in many places; but making it hopelessly complex and less precise by removing the symbols of the language is not going to help. I learned algebra, officially, my freshman year of high school. Yet there are many high school graduates who come out of high school not even having a rudimentary understanding of algebra (and, actually, even basic mathematics - tutored a few of those as well). Many of them learn it in college, so they're obviously capable of learning it. Those high schools failed those students. Many university professors equally fail their students.
But blaming this on the symbols of the language is too far of a stretch for me. Blame the teachers.
Edit: Fixed display of symbols.
You'd use a different notation then. `{1, 2, 4, ..., n^2}` or `{2^0, 2^1, ..., n}` or something similar which indicates what you mean. There's nothing wrong with elipsis, if you spend a second thinking about what you're writing.
If someone wanted to be obnoxious and confusing then they could say: Haha, `n` is not a symbol either; I'm using base-50 and it's digit 23(10). So you can't really stop bad / intentionally misleading communication.
1. https://edu.gcfglobal.org/en/excel2013/complex-formulas/1/
c*c:s*s:x*x
with no ambiguity of parsing whatsoever.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?
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.But then the two lists are the same, and appending to one appends to the other. Not particularly useful.
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.
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...
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.
a = "a"
a = 1
That's dynamic typing. Strong typing means only that a = "a" + 1
fails.More about that at https://en.hexlet.io/courses/intro_to_programming/lessons/ty...
JavaScript and PHP and Perl automatically cast to a reasonable value with all the advantages and pitfalls.
> a = "a" + 1
>fails.
String a = "a" + 1; works in Java. So Java is not strongly typed?
Without overloading the + operator, string concatenation which is a common operation, would have been unnecessarily verbose. Your example without the + operator would look like below:
String a = new String(“a”).concat(1); // String object concat
String a = “a”.concat(1); // String literal concatJava just happens to be less prominent in tech media right now since JS on the server is the new(-ish) hotness. There's still plenty of dislike for it, but really, all that needed saying about it happened long before now.
See C where ints, pointers, floats, and bools can almost all coerce into each other, such that the compiler will allow you to use arithmetic/logic operators with most different types, whether you meant to or not.
Those two attributes are mostly orthogonal.
Dynamic typing refers to checking of types at runtime. JS does not do this consistently or effectively. Python does.
The typeof operator cannot be the basis of a type system since you can count all the answers it gives on your fingers. JS needs a bunch of extra functions like Array.isArray() to determine if something is an array or not. The language itself has no clue, everything that isn't a primitive is just an "object" as far as JS is concerned.
For the rest I don't understand how anybody can seriously make the argument that JS has no types. Shell scripts have no types because almost everything is a character string and that's it. JS type system is very limited but it does exist and I don't see how it can be used to justify {} + [] = 0, which is where we started (especially given that {} and [] are objects in JS, but 0 is a "number", so different core types). Adding two objects and getting a number is not a limitation of the type system, it's a conscious decision by the designers (or at least the side effect of one).
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.
The widespread prevalence of JS transpilers and everyone’s apparent unwillingness to write pure JS makes me think it probably isn’t such a great language. It probably never had a chance given its history with browsers.
Just using it to make the point that every language has valid expressions that make no sense. You can find similar examples in every language. All you have to do to find them is start writing code in a way that no person ever would or should write it.
Especially in the case of dynamically typed languages, throwing an exception sometimes but not always based on the “human intuitiveness” of any given type inference would make the language even more unpredictable. It’s just instead of asking why expression a evaluates to x, we would all be asking why expression a evaluates to x but similar expression b throws an exception.
If you ask me, the latter is even more arbitrary.
These aren’t useful criticisms. The only reason these kind of critiques even get so much attention is because people reading the headline think: “I wonder how the hell that would be evaluated?” And so they click.
The headline is only interesting to begin with because nobody ever writes that, and nobody should ever write that.
If nobody would ever write it or have any expectations about its evaluation, then how is it even significant that the language will interpret it one way vs another?
I think these criticisms are a good springboard to have the debate about static vs dynamic typing, but arguing over whether True == False is False should be evaluated one way vs another is kind of pointless. If the result of that argument is agreement, then we might end up with people actually writing this, which should be the last thing any of us want.
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*/}; +[]I guess JS’s parser explicitly prohibits adding things to a curly-brace enclosed entity?
I’m normally quite defensive of JS, but I’ll have to admit I don’t like that.
You can also do like ({} + []) and it will actually be "adding" them; the confusion solely comes from people typing into the JS console something that wouldn't make any sense to put into an actual script.
({}) + []
-> "[object Object]"
will correctly make it "[object Object]"Did you try to do this?
{a: 1} + []
-> 0
That's interpreted as the statement 1 labeled a, not as an object. This makes it obvious it's not an object: {a: 1, b: 2} + []
-> Uncaught SyntaxError: Unexpected token ':'
JavaScript supports labels so you can continue/break multiple levels at once: {
a: while (true)
while (true)
break a;
}(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!
Prelude> [1, 3 .. 10] :: [Float]
[1.0,3.0,5.0,7.0,9.0,11.0]
In Haskell, this is syntactic sugar for the function enumFromThenTo (in typeclass Enum), which in my view should not have a special case implementation for Float.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.
It's my favourite feature of Python and I actively seek out other languages that make this terrible choice.
I regularly use Python and a couple of curly brace languages and coming back to Python always feels like a breath of fresh air.
> It's my favourite feature of Python
It's like the speed bump: It's for people who can't follow simple rules. It inconveniences you, but is rationalized with that it ultimately makes the world a little safer for everyone, including you.
Me, I unindent temporary code like debug-print statements and literals overriding actual data. This makes it impossible to commit by accident.
But my argument against whitespace sensitivity would be that bad and unreadable code is bad and unreadable regardless of its whitespace. In fact, force-formatting it just hides the evidence.
That’s a good way to put it. Significant indentation makes the syntax so much more lightweight.
This image [0] is supposed to be a joke, but to me it clearly demonstrates how braces and semicolons are both ugly and redundant.
[0] https://www.reddit.com/r/ProgrammerHumor/comments/2wrxyt/
How would you rather write that code? Is it lack of multi-line lambdas that stops you from writing it as you would like?
items
.select(&:is_the_best)
.flat_map { |i| i.sub_item.map(&:value) }
Not having functional constructs really hurts readability, and I don't want to think about what would happen to the list comprehension with more complicated logicSure, you "should" use asyncio instead of promises but honestly it has its own problems, mostly in that it requires the rest of your legacy code base to also be async.
You can have multi-line lambdas, just use parens:
(lambda: look.ma.im
.on_two_lines)
And, yeah, if it's complex enough that it should have control flow, you just write a nested function.And your example is more clearly expressed as a plain loop:
new_list = []
for item in items:
for sub_item in item.sub_items:
if item.is_the_best:
new_list.append(item)
I don't like the comprehension syntax. The correct comprehension is: [item
for item in items
for sub_item in item.sub_items
if item.is_the_best]
Hopefully that makes plain what they were going for, but in practice, list comprehensions are often a contradiction in terms.> Not to mention these random global functions everywhere.
We ought to be able to have it both ways, it should be possible to lift a closure and treat the variables it closes over as arguments, but I wind up doing that manually just so I can test them directly.
> Lines copied into the console care about indentation which is definitely not a fun DX.
I think the issue with breaking copy and paste is the real valid complaint against indentation-sensitive languages. The tooling just isn't there, and this continues to be the case after 20 years.
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 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]()>>> 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...
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
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.
I have no problem with slicing. You can't get it accidentally wrong because the syntax is visible. Negative indexing vs positive indexing can't generally be distinguished just by looking at the indexing statement, which is where I tripped up.
y[end(1)]
y[len - 1]
Either one then constructs a "reverse index" value, and the actual index is determined by the slicing operation. y[end(1)]
is non-composed because AFAICS end(1)
is meaningless by itself. Ditto the second although y[len(y) - 1]
would be closer to the mark.If you're willing to accept a naive implementation that can be optimised, something like
reverse(y)[1]
is compositional though inefficient as-is. You could though recognise this overall and optimise it to not reverse the lot before picking out a single item.Suppose indexing (and you can extend this to slicing if these are possible components of a Range type) is polymorphic.
list[Index] means to find an element at Index, starting at the beginning
list[ReverseIndex] means to find an element at ReverseIndex, starting from the end
Int is naturally a subtype of Index.
end(Int) would be a constructor for some concrete subtype of ReverseIndex that is distinct from Int. Or len would simply be shorthand for ReverseIndex(0). And ReverseIndex is also defined for the usual int arithmetic, except that it returns new values of ReverseIndex.
Alternatively you could do the same with a method I think, something like
y.end[1]
although y.fromEnd[...] might be more mnemonic. I don't see that y[len - 1] could work in this scheme, but whatever.I like yer thinking!
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.
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:
...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 :-)
>>> 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!The 2nd expression is obviously false always.
> 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).
"(x in odds) == expect_odd" gives the intended behaviour and I think is easier to read as well.
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...
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.
Though you can skip this lesson if you've worked with Perl (any others?) in the past.
>>> True is (False is False)
True
>>> True == (False is False)
True >>> (True == False) is False
TrueYeah, that was a real head-scratcher.
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()
... 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
In fact, a problem is that it looks like an operator precedence issue at first glance. Of course, TFA explains the answer.
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.
So that is to say:
a == (b is c) --> ==
/ \
a is
/ \
b c
(a == b) is c --> is
/ \
== c
/ \
a b
a == b is c --> ==_is
/ | \
a b c
Assuming that the == and is are operator tokens and a, b, c are operands, that seems to be one rational hypothesis.Another hypothesis is that == and is are not pure functions that operate on values, but are special operators that operate on syntax, and treat a parenthesized expression differently from an unparenthesized one.
(In what way does this sort of thing belong in a self-proclaimed newbie-friendly language? This has to be a bug.)
If you're working in TXR Lisp, you can use the meq, meql and mequal functions for three or more argument equality.
The call:
(meq a b c ...)
does not mean anything similar to: (and (eq a b) (eq b c) ...)
but the semantics (except for operand evaluation) is like: (or (eq a b) (eq a c) (eq a d) ...)
and likewise for the other two. It's testing whether the left argument is equal to at least one of the remaining arguments.These functions can be called with only one argument, in which case they yield false, just like (or).
These functions are very useful in cond statements that include some tests that prevent conversion into caseq/caseql/casequal.
(cond
((meql x 1 2 3) ... ) ;; x is one of 1 2 3
((> x 10) ...) ;; x is greater than 10
(t ...)) ;; otherwise
m stands for "multi"; or, if you like, it stands for "member" because (meql x a b c) replaces (memql x (list a b c)).That is, I think I'm in violent agreement.
Rather, there is a more fitting adjective, for which a friendly euphemism can be found: "pythonic".
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.
No. IIRC tagged pointers were considered too complex to expose via Python’s C API.
int FIXNUM_P(VALUE object_pointer) { return object_pointer & 1; }
long FIX2LONG(VALUE object_pointer) { return object_pointer >> 1; }
VALUE ruby_value;
long number;
if (FIXNUM_P(ruby_value))
number = FIX2LONG(ruby_value);
I thought this was pretty simple. What does Python's C API look like?https://stackoverflow.com/a/3131208/512904
In Java, there's a clear distinction between a primitive int value and an Integer object. Not all languages have this property. I thought Python didn't.
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.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.