Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"is" operator behaves unexpectedly with integers

Why does the following behave unexpectedly in Python?

>>> a = 256 >>> b = 256 >>> a is b True           # This is an expected result >>> a = 257 >>> b = 257 >>> a is b False          # What happened here? Why is this False? >>> 257 is 257 True           # Yet the literal numbers compare properly 

I am using Python 2.5.2. Trying some different versions of Python, it appears that Python 2.3.3 shows the above behaviour between 99 and 100.

Based on the above, I can hypothesize that Python is internally implemented such that "small" integers are stored in a different way than larger integers and the is operator can tell the difference. Why the leaky abstraction? What is a better way of comparing two arbitrary objects to see whether they are the same when I don't know in advance whether they are numbers or not?

like image 865
Greg Hewgill Avatar asked Nov 20 '08 18:11

Greg Hewgill


People also ask

Can we use is with integer in Python?

Check if float is integer: is_integer() float has is_integer() method that returns True if the value is an integer, and False otherwise. For example, a function that returns True for an integer number ( int or integer float ) can be defined as follows. This function returns False for str .

Are integers Singletons in Python?

We have established that Python indeed is consuming smaller integers through their corresponding singleton instances, without reallocating them every time. Now we verify the hypothesis that Python indeed saves a bunch of allocations during its initialization through these singletons.

Is 1 an integer in Python?

In Python, integers are zero, positive or negative whole numbers without a fractional part and having unlimited precision, e.g. 0, 100, -10.

Is operator in Python matches both type and value?

The is operator does not match the values of the variables, but the instances themselves.


2 Answers

Take a look at this:

>>> a = 256 >>> b = 256 >>> id(a) 9987148 >>> id(b) 9987148 >>> a = 257 >>> b = 257 >>> id(a) 11662816 >>> id(b) 11662828 

Here's what I found in the Python 2 documentation, "Plain Integer Objects" (It's the same for Python 3):

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)

like image 63
Cybis Avatar answered Oct 06 '22 15:10

Cybis


Python's “is” operator behaves unexpectedly with integers?

In summary - let me emphasize: Do not use is to compare integers.

This isn't behavior you should have any expectations about.

Instead, use == and != to compare for equality and inequality, respectively. For example:

>>> a = 1000 >>> a == 1000       # Test integers like this, True >>> a != 5000       # or this! True >>> a is 1000       # Don't do this! - Don't use `is` to test integers!! False 

Explanation

To know this, you need to know the following.

First, what does is do? It is a comparison operator. From the documentation:

The operators is and is not test for object identity: x is y is true if and only if x and y are the same object. x is not y yields the inverse truth value.

And so the following are equivalent.

>>> a is b >>> id(a) == id(b) 

From the documentation:

id Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

Note that the fact that the id of an object in CPython (the reference implementation of Python) is the location in memory is an implementation detail. Other implementations of Python (such as Jython or IronPython) could easily have a different implementation for id.

So what is the use-case for is? PEP8 describes:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

The Question

You ask, and state, the following question (with code):

Why does the following behave unexpectedly in Python?

>>> a = 256 >>> b = 256 >>> a is b True           # This is an expected result 

It is not an expected result. Why is it expected? It only means that the integers valued at 256 referenced by both a and b are the same instance of integer. Integers are immutable in Python, thus they cannot change. This should have no impact on any code. It should not be expected. It is merely an implementation detail.

But perhaps we should be glad that there is not a new separate instance in memory every time we state a value equals 256.

>>> a = 257 >>> b = 257 >>> a is b False          # What happened here? Why is this False? 

Looks like we now have two separate instances of integers with the value of 257 in memory. Since integers are immutable, this wastes memory. Let's hope we're not wasting a lot of it. We're probably not. But this behavior is not guaranteed.

>>> 257 is 257 True           # Yet the literal numbers compare properly 

Well, this looks like your particular implementation of Python is trying to be smart and not creating redundantly valued integers in memory unless it has to. You seem to indicate you are using the referent implementation of Python, which is CPython. Good for CPython.

It might be even better if CPython could do this globally, if it could do so cheaply (as there would a cost in the lookup), perhaps another implementation might.

But as for impact on code, you should not care if an integer is a particular instance of an integer. You should only care what the value of that instance is, and you would use the normal comparison operators for that, i.e. ==.

What is does

is checks that the id of two objects are the same. In CPython, the id is the location in memory, but it could be some other uniquely identifying number in another implementation. To restate this with code:

>>> a is b 

is the same as

>>> id(a) == id(b) 

Why would we want to use is then?

This can be a very fast check relative to say, checking if two very long strings are equal in value. But since it applies to the uniqueness of the object, we thus have limited use-cases for it. In fact, we mostly want to use it to check for None, which is a singleton (a sole instance existing in one place in memory). We might create other singletons if there is potential to conflate them, which we might check with is, but these are relatively rare. Here's an example (will work in Python 2 and 3) e.g.

SENTINEL_SINGLETON = object() # this will only be created one time.  def foo(keyword_argument=None):     if keyword_argument is None:         print('no argument given to foo')     bar()     bar(keyword_argument)     bar('baz')  def bar(keyword_argument=SENTINEL_SINGLETON):     # SENTINEL_SINGLETON tells us if we were not passed anything     # as None is a legitimate potential argument we could get.     if keyword_argument is SENTINEL_SINGLETON:         print('no argument given to bar')     else:         print('argument to bar: {0}'.format(keyword_argument))  foo() 

Which prints:

no argument given to foo no argument given to bar argument to bar: None argument to bar: baz 

And so we see, with is and a sentinel, we are able to differentiate between when bar is called with no arguments and when it is called with None. These are the primary use-cases for is - do not use it to test for equality of integers, strings, tuples, or other things like these.

like image 42
Russia Must Remove Putin Avatar answered Oct 06 '22 15:10

Russia Must Remove Putin