The Python version changes the behavior of the is operator in the tuple.

Asked 2 years ago, Updated 2 years ago, 134 views

The is operator behavior of the tuple changes depending on the version.

[Code used for verification]

x=1,2,2
print(id(x))
y = 1, 2, 2
print(id(y))
print(x is y)

[Results]
Python 2.7.17 Results (xisy is False)

140379358955392
140379359012656
False

Python 3.6.9 Results (xisy is False just like Python 2.7.17)

139911075819024
139911075406400
False

Python 3.7.5 Results (xisy is True)

140641147669392
140641147669392
True

Question 1

From which version does the behavior change?
After trying different versions of Python, the behavior seems to have changed in versions 3.6.9 and 3.7.5 and later.
I looked online to see if there was a similar problem, but I couldn't find it.

Question 2

Are there any other differences in behavior like this?
I was worried if there were any other differences in basic calculations.
Please let me know if there are any reported ones that seem to have a big impact.

python python3 python2

2022-09-30 20:13

2 Answers

*The following is not a direct answer to your question.

Python 3.8.6 will have the same result, so we will proceed with Python 3.8.6.
First, let's talk a little bit about is and id().

static PyObject*
cmp_outcome (PyThreadState*tstate, intop, PyObject*v, PyObject*w)
{
  intres = 0;
  switch(op){
  casePyCmp_IS:
    res=(v==w);
    break;
      :

Comparing memory addresses (pointer values) of objects (classes, instances, methods, functions, etc.).

static PyObject*
builtin_id (PyModuleDef*self, PyObject*v)
/* [clinic end generated code:output=0aa640785f697f65input=5a534136419631f4]*/
{
    PyObject*id = PyLong_FromVoidPtr(v);
                 :

Translating the memory address of the object to an integer object in Python (PyLongObject).

Next, regarding the code in the question section, try disassembly using the dis module.

$python3 --version
Python 3.8.6
$ python3-m disable_equivalency.py
  10 LOAD_CONST0 ((1,2,2))
              2 STORE_NAME0(x)
                   :

  316 LOAD_CONST0(1,2,2))
             18 STORE_NAME3(y)
                   :

The LOAD_CONST (opcode) argument is both 0.This value is an index of func_code.co_consts, but the same means that the value (in this case func_code.co_consts[0]==tuple instance memory address) is the same.

So what happens if I change to x=(1,2,4)?The arguments for LOAD_CONST are different (that is, separate tuple instances):

10 LOAD_CONST0(1,2,4))
              2 STORE_NAME0(x)
                   :

  316 LOAD_CONST1 ((1,2,2))
             18 STORE_NAME3(y)
                   :

The tuple is an immutable instance, but try to disable it (x=[1,2,3],y=[1,2,3]) by changing it to a mutable instance (for example, a list type).

10 LOAD_CONST0(1)
              2 LOAD_CONST1(2)
              4 LOAD_CONST2(3)
              6BUILD_LIST3
              8 STORE_NAME0(x)
                   :

  322 LOAD_CONST0(1)
             24 LOAD_CONST1(2)
             26 LOAD_CONST2(3)
             28 BUILD_LIST3
             30 STORE_NAME3(y)
                   :

BUILD_LIST creates a new list-type instance, so the list is separate, but the element (1,2,3) points to the same element in func_code.co_consts.That is, xisy is False, while x[0]isy[0] is True (as is x[1], x[2].

A side story

The place where you allocate the Tuple instance memory is PyTuple_New().

ifdef is included, but PyTuple_MAXSAVESIZE is defined as .

/*Speed optimization to void frequency malloc/free of small tables*/
# ifndef PyTuple_MAXSAVESIZE
#define PyTuple_MAXSAVESIZE20 / *Largest table to save on free list* /
#endif
# ifndef PyTuple_MAXFREELIST
# define PyTuple_MAXFREELIST2000/* Maximum number of tuples of each size to save*/
#endif

# if PyTuple_MAXSAVESIZE>0
/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists, entry 0 is the empty
   tuple() of which at most one instance will be allotted.
*/
static PyTupleObject* free_list [PyTuple_MAXSAVESIZE];
static int numfree [PyTuple_MAXSAVESIZE];
#endif

The comments say "...to save on free list" or "...malloc/freeof small tuples." PyTuple_New() looks like this and uses free_list (link list of unbinded tuple instances) again.

 if(size<PyTuple_MAXSAVESIZE&(op=free_list[size])!=NULL){
      free_list[size] = (PyTupleObject*) op->ob_item[0];
      numfree [size] --;
                   :
      _Py_NewReference((PyObject*)op);
  }
  else
#endif
  {
                   :
      op=PyObject_GC_NewVar(PyTupleObject, & PyTuple_Type, size);
      if(op==NULL)
        return NULL;
  }

Examples of reuse of a Tuple instance include:

$python3
Python 3.8.6 (default, Sep 25 2020, 09:36:53) 
[GCC 10.2.0] on linux
>>>x = (1, 2, 3)
>>>x_id=id(x)
>>>x_id
139730736978368
>>delx
>>y=(-1, -2, -3)
>>>id(y)
139730736978368
>>>id(y)==x_id
True


2022-09-30 20:13

is is used to verify that objects do not match and should not be used to compare tuples. To compare tuples, you must first use the == operator.

You probably won't know where the behavior changed unless you follow the source code, but a version upgrade that has such an impact should be widely known from 3.x to 4.x.That's why the original usage is wrong.

Add

Tuples with the same value appear to be cached.
python-Why don't you get the same ID when assigned the same values?- Stack Overflow
It doesn't depend on the version, but on the memory conditions at the time.

Additional 2

First, let's talk about objects.3.1.Objects, Values, and Types

All attributes have identity, type, and value. The identity does not change after it is generated.You might think that this is like the address of an object. The 'is' operator compares the identity of the two objects. The id() function returns an integer representing identity.

Object identification and value identification are related but not exactly the same.Also

In CPython, id(x) returns the address in memory where x is stored.

As you can see, comparing addresses in memory does not necessarily mean they are the same.

Next, look at 6.10.1. Compare Values.

Equivalent comparison (== and !)The default behavior of =) is based on object identity. Thus, the results of the equivalent comparison of the same instance are equal, and the results of the equivalent comparison of the different instances are not equal. The default behavior was to make all objects reflective (xis y if xis y).

xis yimplies is easier to understand, but if xis y is true, it means xis y, including .In other words, xisy <x==y (although not mathematically correct).In other words, xisy does not necessarily mean x==y.

Python treats all values as objects (where mro() is a function that returns the class back to the inheritance relationship).

>>>type((1,2,3)).mro()
<class 'tuple' >, <class 'object' >

As mentioned above, objects do not always have the same values, even if they remain identical.Python is important for all objects, not just tuples.


2022-09-30 20:13

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.