591

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

Python2.5.2を使用しています。Pythonのいくつかの異なるバージョンを試してみると、Python2.3.3は99から100の間で上記の動作を示しているようです。

上記に基づいて、Pythonは内部的に実装されており、「小さい」整数は大きい整数とは異なる方法で格納され、is演算子はその違いを認識できると仮定できます。なぜ漏れのある抽象化?2つの任意のオブジェクトを比較して、それらが数字であるかどうかを事前に知らない場合に、それらが同じであるかどうかを確認するためのより良い方法は何ですか?

4

11 に答える 11

432

これを見てください:

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

Python 2 のドキュメント「Plain Integer Objects」で見つけたものを次に示します ( Python 3でも同じです)。

現在の実装では、-5 から 256 までのすべての整数に対して整数オブジェクトの配列が保持されます。その範囲で int を作成すると、実際には既存のオブジェクトへの参照が返されます。したがって、1 の値を変更できるはずです。この場合の Python の動作は未定義であると思われます。:-)

于 2008-11-20T18:30:20.017 に答える
139

Python の「is」演算子は整数に対して予期しない動作をしますか?

要約すると、強調させてください。整数の比較には使用しないでください。is

これは、期待するべき動作ではありません。

代わりに、 と を使用==!=て、それぞれ等しいかどうかを比較します。例えば:

>>> 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

説明

これを知るには、次のことを知る必要があります。

まず、何をしisますか?比較演算子です。ドキュメントから:

オブジェクト同一性の演算子isand is nottest:x is yは、x と y が同じオブジェクトである場合にのみ true になります。x is not y逆の真理値が得られます。

したがって、以下は同等です。

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

ドキュメントから:

id オブジェクトの「アイデンティティ」を返します。これは、このオブジェクトの有効期間中に一意で一定であることが保証されている整数 (または長整数) です。有効期間が重複しない 2 つのオブジェクトは、同じid()値を持つ場合があります。

CPython (Python の参照実装) のオブジェクトの id がメモリ内の場所であるという事実は、実装の詳細であることに注意してください。Python の他の実装 (Jython や IronPython など) では、id.

では、ユースケースはis何ですか? PEP8 の説明:

のようなシングルトンとの比較は、None常にisor を使用して行う必要がis notあり、等値演算子は決して使用しないでください。

質問

次の質問をします (コード付き)。

以下が Python で予期しない動作をするのはなぜですか?

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

これは期待される結果ではありません。なぜそれが期待されるのですか?これは、との256両方で参照される整数値が整数の同じインスタンスであることを意味するだけです。整数は Python では不変であるため、変更できません。これは、どのコードにも影響を与えません。それは期待されるべきではありません。これは単なる実装の詳細です。ab

しかし、値が 256 に等しいと宣言するたびに、メモリ内に新しい別のインスタンスが存在しないことを喜ばしく思います。

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

メモリ内に の値を持つ整数の 2 つの個別のインスタンスがあるように見え257ます。整数は不変であるため、これはメモリを浪費します。あまり無駄にしないことを祈りましょう。私たちはおそらくそうではありません。ただし、この動作は保証されていません。

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

これは、Python の特定の実装がスマートになろうとしているように見えます。必要でない限り、冗長な値の整数をメモリに作成していません。CPython である Python の参照実装を使用していることを示しているようです。CPython に適しています。

CPython がこれをグローバルに実行できればさらに良いかもしれませんが、(ルックアップにコストがかかるため) 安価に実行できるなら、おそらく別の実装がそうかもしれません。

ただし、コードへの影響については、整数が整数の特定のインスタンスであるかどうかを気にする必要はありません。そのインスタンスの値が何であるかだけを気にする必要があり、そのためには通常の比較演算子、つまり を使用します==

is

isid2 つのオブジェクトの が同じであることを確認します。CPython では、idはメモリ内の場所ですが、別の実装では一意に識別される別の番号である可能性があります。これをコードで言い換えるには:

>>> a is b

と同じです

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

isそれでは、なぜ使用したいのでしょうか。

これは、たとえば、2 つの非常に長い文字列の値が等しいかどうかをチェックする場合に比べて、非常に高速なチェックになります。しかし、それはオブジェクトの一意性に適用されるため、その使用例は限られています。None実際、シングルトン (メモリ内の 1 つの場所に存在する唯一のインスタンス) であるをチェックするために主に使用したいと考えています。それらを混同する可能性がある場合は、他のシングルトンを作成する可能性があります。これは で確認できますisが、これらは比較的まれです。例を次に示します (Python 2 および 3 で動作します)。

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()

どちらが印刷されますか:

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

そして、と センチネルを使用すると、 が引数なしで呼び出された場合と で呼び出されisた場合を区別できることがわかります。これらは主な使用例です-整数、文字列、タプル、またはこれらのような他のものの等価性をテストするために使用しないでください。barNoneis

于 2015-03-04T20:20:12.797 に答える
69

遅くなりましたが、回答の情報源が必要ですか? より多くの人が従うことができるように、これを紹介的な方法で表現しようとします.


CPython の良いところは、このソースを実際に見ることができることです。3.5リリースのリンクを使用しますが、対応する2.xのリンクを見つけるのは簡単です。

CPython では、新しいオブジェクトの作成を処理するC-API関数は. この関数の説明は次のとおりです。intPyLong_FromLong(long v)

現在の実装では、-5 から 256 までのすべての整数に対して整数オブジェクトの配列が保持されます。その範囲で int を作成すると、実際には既存の object への参照が返されます。したがって、1 の値を変更できるはずです。この場合の Python の動作は未定義であると思われます。:-)

(イタリック体)

あなたのことは知りませんが、私はこれを見て考えました:その配列を見つけよう!

CPythonを実装する C コードをいじっていない場合は、次のようにする必要があります。すべてがかなり整理されていて読みやすいです。この場合、メイン ソース コード ディレクトリ ツリーのObjectsサブディレクトリを調べる必要があります。

PyLong_FromLongオブジェクトを扱うlongので、内部をのぞく必要があると推測するのは難しくありませんlongobject.c。内部を見た後、物事は混沌としていると思うかもしれません。それらはそうですが、恐れることはありません。私たちが探している関数は、230 行目で身も凍るような状態で、チェックアウトするのを待っています。これは小さめの関数なので、本体 (宣言を除く) はここに簡単に貼り付けられます。

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

さて、私たちは Cマスター コード haxxorzではありませんが、愚かでもありませんCHECK_SMALL_INT(ival);。これと関係があることは理解できます。それをチェックしよう:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

get_small_intしたがって、値ivalが条件を満たす場合に関数を呼び出すマクロです。

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

NSMALLNEGINTSとは何NSMALLPOSINTSですか?マクロ!ここにそれらがあります

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

したがって、条件はif (-5 <= ival && ival < 257)callget_small_intです。

次に、get_small_intそのすべての栄光を見てみましょう (まあ、興味深いのはその本体だけです)。

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

よし、 aPyObjectを宣言し、前の条件が保持されていることをアサートし、代入を実行します。

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_intsこれは、私たちが探し求めてきた配列に非常によく似ています。いまいましいドキュメントを読むだけで、ずっとわかっていたはずです! :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

そうです、これは私たちの男です。int範囲内に新しいオブジェクトを作成する場合[NSMALLNEGINTS, NSMALLPOSINTS)は、事前に割り当てられた既存のオブジェクトへの参照を取得するだけです。

参照は同じオブジェクトを参照するため、id()直接発行するか、同一性をチェックするとis、まったく同じものが返されます。

しかし、いつ割り当てられますか??

Python での初期化中_PyLong_Initに、喜んで for ループに入り、これを行います。

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

ループ本体を読むためにソースをチェックしてください!

私の説明で、あなたがCのことを明確に理解できたことを願っています (しゃれは明らかに意図されています)。


しかし、257 is 257?調子はどう?

これは実際には説明が簡単で、私はすでにそうしようとしました。これは、Python がこのインタラクティブなステートメントを単一のブロックとして実行するためです。

>>> 257 is 257

このステートメントのコンパイル中に、CPython は 2 つの一致するリテラルがあることを認識し、同じPyLongObject表現を使用し257ます。これは、自分でコンパイルしてその内容を調べるとわかります。

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

CPython が操作を実行すると、まったく同じオブジェクトが読み込まれます。

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

だからis戻ってきTrueます。

于 2016-01-23T13:26:00.273 に答える
61

2 つのものが等しいかどうか、または同じオブジェクトかどうかを確認するかどうかによって異なります。

isそれらが等しいだけでなく、同じオブジェクトであるかどうかを確認します。小さな整数は、おそらくスペース効率のために同じメモリ位置を指しています

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

==任意のオブジェクトの等価性を比較するために使用する必要があります。__eq__、および__ne__属性で動作を指定できます。

于 2008-11-20T18:36:06.977 に答える
38

ソース ファイルintobject.cで確認できるように、Python は効率のために小さい整数をキャッシュします。小さい整数への参照を作成するたびに、新しいオブジェクトではなく、キャッシュされた小さい整数を参照しています。257 は小さい整数ではないため、別のオブジェクトとして計算されます。

==その目的のために使用する方が良いです。

于 2008-11-20T19:50:11.337 に答える
21

あなたの仮説は正しいと思います。id(オブジェクトのアイデンティティ)を試してみてください:

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

数字<= 255はリテラルとして扱われ、上記のものはすべて異なる方法で扱われるようです。

于 2008-11-20T18:29:48.583 に答える
14

int、string、datetime などの不変値オブジェクトの場合、オブジェクト ID は特に役に立ちません。平等に考えたほうがいい。ID は本質的に値オブジェクトの実装の詳細です。値オブジェクトは不変であるため、同じオブジェクトまたは複数のオブジェクトへの複数の参照を持つことの間に実質的な違いはありません。

于 2008-11-21T01:58:53.853 に答える
8

is 恒等等価演算子です ( のように機能しますid(a) == id(b)); 2 つの等しい数が必ずしも同じオブジェクトであるとは限りません。パフォーマンス上の理由から、一部の小さな整数はたまたまメモ化されるため、それらは同じになる傾向があります (これらは不変であるため、これを行うことができます)。

===一方、PHP のx == y and type(x) == type(y)演算子は、 Paulo Freitas のコメントによると、等価性と型をチェックするものとして説明されています。これは一般的な数値には十分ですが、ばかげた方法でis定義するクラスとは異なります。__eq__

class Unequal:
    def __eq__(self, other):
        return False

PHP では、「組み込み」クラス (PHP ではなく、C レベルで実装されていることを意味すると私は考えています) に対して同じことを許可しているようです。少しばかばかしくない使い方としては、数値として使用されるたびに異なる値を持つタイマー オブジェクトがあります。それがI don't knowNowによる評価であることを示すのではなく、Visual Basic をエミュレートしたい理由はまったくありません。time.time()

Greg Hewgill (OP) は、明確なコメントを 1 つ作成しました。

==と比較するかどうかを選択するために、物事を数値として分類する必要があるため、これにはさらに別の答えがありますisCPythonは PyNumber_Check を含むnumber protocolを定義していますが、これは Python 自体からはアクセスできません。

私たちが知っているすべての数値型で使用しようとすることができますisinstanceが、これは必然的に不完全です。types モジュールには StringTypes リストが含まれていますが、NumberTypes は含まれていません。Python 2.6 以降、組み込みの数値クラスには基本クラスnumbers.Numberがありますが、同じ問題があります。

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

ところで、NumPyは低い数値の個別のインスタンスを生成します。

この質問の変種に対する答えは実際にはわかりません。理論的には ctypes を使用して を呼び出すことができると思いますPyNumber_Checkが、その関数でさえ議論されており、確かに移植性がありません。今のところ、何をテストするかについてはそれほどこだわる必要はありません。

結局のところ、この問題は Python が元々、 Scheme の number?Haskell の 型クラス Numのような述語を持つ型ツリーを持っていないことに起因しています。is値の等価性ではなく、オブジェクトの同一性をチェックします。PHP にも多彩な歴史があり、===明らかに PHP5 のisオブジェクトに対してのみ動作し、PHP4 では動作しません。これは、言語間 (1 つのバージョンを含む) を移動することの増大する苦痛です。

于 2013-03-20T11:20:46.180 に答える
4

文字列でも発生します。

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

今はすべて問題ないようです。

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

それも想定内。

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

今では予想外です。

于 2015-10-14T15:53:05.600 に答える
4

Python 3.8 の新機能: Python の動作の変更点:

特定のタイプのリテラル (string、int など) でID チェック (および ) が使用されると、コンパイラはSyntaxWarningを生成するようになりました。これらは CPython で偶然に動作することがよくありますが、言語仕様では保証されていません。この警告は、代わりに等価テスト ( および) を使用するようにユーザーにアドバイスします。isis not==!=

于 2019-08-24T20:34:16.037 に答える