

バージョンによってtupleのis演算子の振る舞いが変わります。

【確認に使用したコード】

[code]

    x = 1, 2, 2
    print(id(x))
    y = 1, 2, 2
    print(id(y))
    print(x is y)
    
[/code]

【実行結果】  
Python 2.7.17の結果(x is yがFalse)

[code]

    140379358955392
    140379359012656
    False
    
[/code]

Python 3.6.9の結果(Python 2.7.17と同じくx is yがFalse)

[code]

    139911075819024
    139911075406400
    False
    
[/code]

Python 3.7.5の結果(x is yがTrue)

[code]

    140641147669392
    140641147669392
    True
    
[/code]

### 質問1

振る舞いが変わるのはどのバージョンからでしょうか？  
バージョンの異なるPythonで試してみた結果、3.6.9より新しく、3.7.5以下のバージョンで振る舞いが変わったようです。  
同様の問題がないかネットで調べてみたのですが、見つかりませんでした。

### 質問2

他にもこのような振る舞いの違いはありますか？  
基本的な演算で、他にもこのような違いがあるか心配になりました。  
報告されているもので影響が大きそうなものがあれば教えて欲しいです。


CC BY-SA 4.0
2020-12-26T10:26:53.503
72905
2020-12-27T02:41:36.287
2020-12-27T02:41:36.287
35558
35558
question
python python3 python2
Python バージョンによってtupleのis演算子の振る舞いが変わる


`is` はオブジェクトが一致しないことを確かめる目的で使われるものであり、 **タプルの比較に使ってはいけません** 。タプルの比較を行うためにはそもそも
**値の比較は`==` 演算子を使わなければいけません**。

どこで振る舞いが変わったかについてはソースコードを追わなければおそらくわかりませんが、それほどまでに影響のあるバージョンアップなら広く知らされて 3.x
から 4.x になるはずです。なのでそもそもの使い方が間違っているのです。

### 追記

同じ値のタプルはキャッシュされているようです。  
[python - Why don't tuples get the same ID when assigned the same values? -
Stack Overflow](https://stackoverflow.com/questions/49327859/why-dont-tuples-
get-the-same-id-when-assigned-the-same-values)  
バージョンによって変わるのではなく、その時のメモリの状況によって ID が変わるのです。

### 追記 2

まずはオブジェクトについて解説します。[3.1.
オブジェクト、値、および型](https://docs.python.org/ja/3/reference/datamodel.html#objects)にあるように

> すべての属性は、同一性 (identity)、型、値をもっています。 同一性
> は生成されたあとは変更されません。これはオブジェクトのアドレスのようなものだと考えられるかもしれません。 'is'
> 演算子は2つのオブジェクトの同一性を比較します。 id() 関数は同一性を表す整数を返します。

オブジェクトの同一性と値の同一性は関連はありますが全く同じではありません。また

> CPython では、id(x) は x が格納されているメモリ上のアドレスを返します。

とあるように、メモリ上のアドレスを比較しても値が同じとは限らないことがわかります。

次に[6.10.1.
値の比較](https://docs.python.org/ja/3/reference/expressions.html#value-
comparisons)を見ましょう。

> 等価比較 (== および !=) のデフォルトの振る舞いは、オブジェクトの同一性に基づいています。
> 従って、同一のインスタンスの等価比較の結果は等しいとなり、同一でないインスタンスの等価比較の結果は等しくないとなります。
> デフォルトの振る舞いをこのようにしたのは、全てのオブジェクトを反射的 (reflexive つまり `x is y` ならば `x == y`)
> なものにしたかったからです。

`x is y` implies `x == y` と原文のほうがわかりやすいですが、`x is y` が成り立つならば `x == y` という意味を
**含む** と言っています。つまり（数学的に正しい表記ではないですが） `x is y` ⊇ `x == y` と言っているのです。すなわち **`x
is y` だからといって `x == y` とは限らない**ことを明確に述べています。

Python では値も全てオブジェクト扱いされています（`mro()` は継承関係をさかのぼってクラスを返す関数）。

[code]

    >>> type((1,2,3)).mro()
    [<class 'tuple'>, <class 'object'>]
    
[/code]

前述のようにオブジェクトは同一性が保たれていても値が同じとは限らないのです。タプルに限らず Python 全てのオブジェクトに関わる大事なことです。


CC BY-SA 4.0
2020-12-26T11:45:05.330
72906
2020-12-26T16:04:37.047
2020-12-26T16:04:37.047
39645
39645
72905
answer


※ 以下は質問に対する直接の回答ではありません。

Python 3.8.6 でも同様の結果になりますので、Python 3.8.6 で話を進めます。  
まず最初に `is` と `id()` に関して少し説明します。

  * [is](https://github.com/python/cpython/blob/3.8/Python/ceval.c#L5070)

[code]

    static PyObject *
    cmp_outcome(PyThreadState *tstate, int op, PyObject *v, PyObject *w)
    {
      int res = 0;
      switch (op) {
      case PyCmp_IS:
        res = (v == w);
        break;
          :
    
[/code]

オブジェクト(クラス、インスタンス、メソッド、関数、etc.)のメモリアドレス(ポインタ値)を比較しています。

  * [id()](https://github.com/python/cpython/blob/3.8/Python/bltinmodule.c#L1170)

[code]

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

オブジェクトのメモリアドレスを Python の整数型オブジェクト(`PyLongObject`)に変換しています。

次に質問欄にあるコードですが、dis モジュールを使って disassemble を行ってみます。

[code]

    $ python3 --version
    Python 3.8.6
    $ python3 -m dis tuple_equivalency.py
      1           0 LOAD_CONST               0 ((1, 2, 2))
                  2 STORE_NAME               0 (x)
                       :
    
      3          16 LOAD_CONST               0 ((1, 2, 2))
                 18 STORE_NAME               3 (y)
                       :
    
[/code]

`LOAD_CONST`(opcode) の引数がどちらも `0` になっています。この値は `func_code.co_consts`
のインデックスですが、同じという事はその値(この場合は `func_code.co_consts[0]` == tuple
インスタンスのメモリアドレス)が同一であるという事になります。

それでは `x = (1, 2, 4)` に変更するとどうなるのでしょうか。以下の様に、`LOAD_CONST` の引数が異なっています(つまり別個の
tuple インスタンス)。

[code]

      1           0 LOAD_CONST               0 ((1, 2, 4))
                  2 STORE_NAME               0 (x)
                       :
    
      3          16 LOAD_CONST               1 ((1, 2, 2))
                 18 STORE_NAME               3 (y)
                       :
    
[/code]

tuple は immutable なインスタンスですが、mutable なインスタンス(たとえば list 型)に変更して(`x = [1, 2,
3]`, `y = [1, 2, 3]`) disassemble してみます。

[code]

      1           0 LOAD_CONST               0 (1)
                  2 LOAD_CONST               1 (2)
                  4 LOAD_CONST               2 (3)
                  6 BUILD_LIST               3
                  8 STORE_NAME               0 (x)
                       :
    
      3          22 LOAD_CONST               0 (1)
                 24 LOAD_CONST               1 (2)
                 26 LOAD_CONST               2 (3)
                 28 BUILD_LIST               3
                 30 STORE_NAME               3 (y)
                       :
    
[/code]

`BUILD_LIST` によって新たな list 型インスタンスが作成されますので、list としては別々になりますが、その要素(`1, 2, 3`)は
`func_code.co_consts` の同じ要素を指しています。つまり、`x is y` は `False` になりますが、`x[0] is
y[0]` は `True` になります(`x[1]`, `x[2]` も同様)。

### 余談

Tuple インスタンスを memory allocate
している箇所は[PyTuple_New()](https://github.com/python/cpython/blob/3.8/Objects/tupleobject.c#L80)になります。

`ifdef` が入っていますが、`PyTuple_MAXSAVESIZE`
は[以下の様に定義されています](https://github.com/python/cpython/blob/3.8/Objects/tupleobject.c#L18)。

[code]

    /* Speed optimization to avoid frequent malloc/free of small tuples */
    #ifndef PyTuple_MAXSAVESIZE
    #define PyTuple_MAXSAVESIZE     20  /* Largest tuple to save on free list */
    #endif
    #ifndef PyTuple_MAXFREELIST
    #define PyTuple_MAXFREELIST   2000  /* 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 allocated.
    */
    static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
    static int numfree[PyTuple_MAXSAVESIZE];
    #endif
    
[/code]

ここで、コメントには "... to save on **free list** " とか "... **malloc/free** of small
tuples" と書かれています。`PyTuple_New()` は以下の様になっていて、`free_list`(unbind された tuple
インスタンス の link list)を使って tuple インスタンスを再利用しています。

[code]

      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;
      }
    
[/code]

Tuple インスタンスが再利用される例としては以下になります。

[code]

    $ 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
    >>> del x
    >>> y = (-1, -2, -3)
    >>> id(y)
    139730736978368
    >>> id(y) == x_id
    True
    
[/code]


CC BY-SA 4.0
2020-12-26T22:22:41.290
72911
2020-12-26T22:22:41.290
72905
answer