frost101
frost101

Reputation: 31

Why does the 'is' operator behave unexpectedly with arithmetically equal expressions

After reading this and this, I still cannot understand the following behaviour:

a = 1000
b = 1000
print (a == b)
print (a is b)
print (f"id(a) = {id(a)} \nid(b) = {id(b)}")

As expected I get

True
True
id(a) = 2806705928816 
id(b) = 2806705928816

But when i try to do something like this:

a = 1000
b = 1000 + a - a
print (a == b)
print (a is b)
print (f"id(a) = {id(a)} \nid(b) = {id(b)}")

I got False in expression a is b

True
False
id(a) = 3030783801968 
id(b) = 3030783802064

Why does a variable behave differently when assigning the result of an expression over a integer and an expression with other variables to it? Although mathematically this gives the same integer.

Upvotes: 2

Views: 102

Answers (4)

Mike Williamson
Mike Williamson

Reputation: 3178

You already have a few accurate answers. Here I am giving a "back to basics" answer.


What is ==?

Python == means is the value on the left the same as the value on the right.

sum([5, 7]) == (48 * 3)**0.5

is True. It requires several evaluation steps to make each expression reach the value of 12. Even then, the integer 12 is being compared to the float 12.0, so a final conversion of the integer to a float is necessary.

The key takeaway: each expression is evaluated and the resulting values are compared. If they are equal, then the expression is true.

What is is?

Python is, on the other hand, means is the name on the left pointing to the same object as the name on the right.

a = 3.14159
b = a
a is b

is True. a has been assigned to the value 3.14159. But more to the point, there is a block of memory holding an object, which in this case is the float 3.14159. a points to that object / block of memory. b points to a, which means that it points to that same block of memory.


You can very easily test this: create two "names" that simply point to a number, and compare them using is, and they will not match:

>>> a = 1239481203948
>>> b = 1239481203948
>>> a is b
False

This is false because we now have two different locations in memory / objects pointing to each of them:

>>> id(a)
140402381635344
>>> id(b)
140402391174416

(On your machine, you will get a different set of ids.)

So, in effect, you have "wasted" space because you have two objects taking up space for the same information.

But wait, there's more

If you play around with this on your own, you will find tons of exceptions to what I wrote, and confuse yourself. Here are just a few:

>>> a = 157
>>> b = 157
>>> a is b
True

What?? Why is this true? To optimize Python, the "most common numbers" have been optimized. I may be wrong, but I recall that there is designated space in memory for the most common numbers. And those are the first few hundred integers, and a few others.

But there are other optimizations, too:

>>> a = None
>>> b = None
>>> a is b
True

>>> a = True
>>> b = True
>>> a is b
True

These are all still following the same rule as I stated earlier: the reason why is evaluates to True is because a and b are both pointing to the same location in memory / object.

This happens in these odd cases because of optimizations in Python. But generically speaking, the only way to ensure is evaluates to True is if a name is assigned to an object that already has a name, like when we wrote:

>>> a = 3.14159
>>> b = a
>>> a is b
True

instead of writing

>>> a = 3.14159
>>> b = 3.14159
>>> a is b
False

Upvotes: 1

Rishabh Kumar
Rishabh Kumar

Reputation: 2430

When you do something like :

(case-1)

a = 1000
b = a

or (case-2)

a = 1000
b = 1000

Python is smart enough to know before hand that even after execution you won't need new memory.

So, python just before execution makes b an alias of a in the first case.

The second case is bit different. Python is a true object oriented language, the literal 1000 is treated as an object. (Intuitively you can think as 1000 to be name of a const object).

So in second case a and b are technically, both becoming alias of 1000

Now in your example:

a = 1000
b = 1000 + a - a
print (a == b)
print (a is b)

while assignment of b, python doesn't know before hand what is going to be the value of a. When I say before-hand I mean before any form of calculation being started. So python reserves a new memory location for band then saves the output of the operation in this new memory location.

It is also worth noting this:

4-1 is 3
True

In this case, python doesn't saves this line with 4-1 but processes it before compilation to be 3, for runtime optimisation.

Upvotes: 3

Rambarun Komaljeet
Rambarun Komaljeet

Reputation: 646

Python executes a statement by evaluating its expressions to values one by one, then performing some operation on those values.

Source: https://courses.cs.washington.edu/courses/cse140/13wi/eval_rules.pdf

Basically b = 1000 + a - a is not being done in one go, but in multiple evaluations and python stores the results for b at each evaluation in a different memory location than a. At this point a and b are different objects.

use == for equality checks.

use "is" to check if objects are same (variables are referencing same memory location).

Upvotes: 0

Ade_1
Ade_1

Reputation: 1486

the difference is in the reference to location. '==' checks for equality in terms of data type and value however, 'is; reference the location of variable in memory.

is will return false for the below

id(a) = 3030783801968 <----
id(b) = 3030783802064 <----

is will return true for the below

    id(a) = 2806705928816 <----
    id(b) = 2806705928816 <----

Upvotes: 0

Related Questions