web-dev-qa-db-ja.com

「is」演算子は整数で予期しない動作をします

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

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

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

463
Greg Hewgill

これを見てください:

>>> 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の動作は未定義だと思います。 :-)

356
Cybis

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は何をしますか?これは比較演算子です。 ドキュメント から:

演算子isおよびis notは、オブジェクトの同一性をテストします: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のようなシングルトンとの比較は、常にisまたはis notを使用して実行する必要があり、等値演算子は使用しないでください。

質問

次の質問を(コードを使用して)行い、述べます。

次のような動作がPythonで予期しない動作をするのはなぜですか?

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

not期待される結果ではありません。なぜそれが予想されるのですか?これは、abの両方によって参照される256で評価される整数が、整数の同じインスタンスであることを意味します。整数はPythonでは不変であるため、変更できません。これはコードに影響を与えません。予期しないはずです。単なる実装の詳細です。

しかし、おそらく、256に等しい値を指定するたびに、メモリに新しい個別のインスタンスが存在しないことを喜んでいるはずです。

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

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

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

ええ、これはPythonの特定の実装がスマートにしようとしており、必要でない限り、メモリ内に冗長な値の整数を作成していないようです。 Pythonのリファレント実装(CPython)を使用していることを示しているようです。 CPythonに適しています。

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

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

isが行うこと

isは、2つのオブジェクトのidが同じであることを確認します。 CPythonでは、idはメモリ内の場所ですが、別の実装では他の一意に識別できる番号になる可能性があります。これをコードで再記述するには:

>>> a is b

と同じです

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

なぜisを使用するのでしょうか?

これは、2つの非常に長い文字列の値が等しいかどうかをチェックするなど、非常に高速なチェックになります。しかし、それはオブジェクトの一意性に適用されるため、そのための使用例は限られています。実際、ほとんどの場合、これを使用して、シングルトン(メモリ内の1つの場所に存在する唯一のインスタンス)であるNoneを確認します。それらを統合する可能性がある場合は、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とセンチネルを使用して、barが引数なしで呼び出される場合と、Noneで呼び出される場合を区別できます。これらはisの主な使用例です。donotを使用して、整数、文字列、タプル、またはこれらのようなものの同等性をテストします。

95
Aaron Hall

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

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

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

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

56
JimB

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

そのためには、==を使用することをお勧めします。

36
Angel

あなたの仮説は正しいと思います。 id(オブジェクトの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はリテラルとして扱われ、上記のすべてが異なるように扱われるようです!

19
Amit

Int、strings、datetimesなどの不変の値オブジェクトの場合、オブジェクトIDは特に役立ちません。平等について考える方が良いです。アイデンティティは本質的に値オブジェクトの実装の詳細です-それらは不変であるため、同じオブジェクトまたは複数のオブジェクトへの複数の参照を持つのに効果的な違いはありません。

12
babbageclunk

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

一方、 PHP's===演算子は、Paulo Freitasのコメントに従って、等式と型をチェックするものとして説明されています:x == y and type(x) == type(y)。これは一般的な数では十分ですが、__eq__を不条理に定義するクラスのisとは異なります。

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

PHPは「ビルトイン」クラス(PHPではなくCレベルで実装されることを意味します)にも同じことを許可しています。少し不合理な使用法はタイマーオブジェクトかもしれません。これは、数値として使用されるたびに異なる値を持ちます。 time.time()の評価であることを示すのではなく、Visual BasicのNowをエミュレートしたい理由はわかりません。

Greg Hewgill(OP)は、「私の目標は、価値の平等ではなく、オブジェクトの同一性を比較することです。数値を除き、オブジェクトの同一性を価値の平等と同じように扱いたい」という明確なコメントを述べました。

==と比較するかisと比較するかを選択するために、物事を数字として分類するかどうかにかかわらず、これにはさらに別の答えがあります。 CPython は、PyNumber_Checkを含む numberプロトコル を定義しますが、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 は、低い数値の個別のインスタンスを生成します。

私は実際にこの質問の変種への答えを知りません。理論的にはc_typesを使用してPyNumber_Checkを呼び出すことができると思いますが、その関数 でも議論されました 、そして確かに移植性がありません。私たちは、今のところテストするものについてあまり気にする必要はありません。

結局、この問題はPythonに元々 Scheme'snumber?のような述語を持つタイプツリーがないことに起因しています。/Haskell'stype classNumisは、値の等価性ではなく、オブジェクトの同一性をチェックします。 PHPにもカラフルな履歴があります。===は、PHP5のオブジェクト でのみisとして動作しますが、PHP4 では動作しません。これは、複数の言語(1つのバージョンを含む)を移動する際の痛みです。

8
Yann Vernier

既存の回答のいずれにも指摘されていない別の問題があります。 Pythonは、2つの不変の値をマージすることが許可されており、事前に作成された小さなint値だけがこれを実現する方法ではありません。 Pythonの実装は、これを行うために保証されることはありませんが、それらはすべて、小さなint以外のものに対しても行われます。


まず、空のTuplestr、およびbytesなどの事前作成された値と、いくつかの短い文字列(CPython 3.6では、256個の単一の文字Latin-1文字列)。例えば:

>>> a = ()
>>> b = ()
>>> a is b
True

ただし、事前に作成されていない値でも同じになる場合があります。これらの例を考慮してください:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

そして、これはint値に限定されません:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

明らかに、CPythonには42.23e100float値が事前に作成されていません。それで、ここで何が起こっているのでしょうか?

CPythonコンパイラは、同じコンパイル単位で、intfloatstrbytesなどの既知の不変型の定数値をマージします。モジュールの場合、モジュール全体がコンパイル単位ですが、対話型インタープリターでは、各ステートメントは個別のコンパイル単位です。 cdは別々のステートメントで定義されているため、それらの値はマージされません。 efは同じステートメントで定義されているため、それらの値はマージされます。


バイトコードを逆アセンブルすると、何が起こっているのかを確認できます。 e, f = 128, 128を実行する関数を定義してから、その関数でdis.disを呼び出すと、単一の定数値(128, 128)があることがわかります。

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

実際にバイトコードで使用されていない場合でも、コンパイラは128を定数として保存していることに気付くかもしれません。これにより、CPythonのコンパイラは最適化をほとんど行わないことがわかります。これは、(空でない)タプルが実際にはマージされないことを意味します:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

それを関数dis itに入れ、co_constsを見てください。12があり、同じ_を共有する2つの(1, 2)タプルがあります12が同一ではなく、2つの異なる等しいタプルを持つ((1, 2), (1, 2))タプル。


CPythonが行う最適化がもう1つあります。文字列インターンです。コンパイラ定数の折りたたみとは異なり、これはソースコードリテラルに限定されません。

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

一方、strタイプ、および 内部ストレージの種類 "ascii compact"、 "compact"、または "legacy ready" の文字列に制限され、多くの場合「ascii compact」のみが抑留されます。


いずれにせよ、値のルールは、実装ごとに、また実装ごとに、また同じ実装のバージョン間で、さらには同じ実装の同じコピーで同じコードを実行する間でも異なる場合があります。 。

1つの特定のPythonのルールを学習する価値がある場合があります。しかし、コードでそれらに依存する価値はありません。唯一の安全なルールは次のとおりです。

  • 2つの等しいが別々に作成された不変値が同一であると想定するコードを記述しないでください。
  • 2つの等しいが別々に作成された不変の値が異なると想定するコードを記述しないでください。

または、言い換えると、isを使用して、文書化されたシングルトン(Noneなど)をテストするか、コード内の1か所でのみ作成されます(_sentinel = object()イディオムなど)。

7
abarnert

文字列でも起こります:

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

今、それは予想外です。

4
sobolevn

ご覧ください こちら

現在の実装では、-5〜256のすべての整数に対して整数オブジェクトの配列が保持されます。その範囲でintを作成すると、実際には既存のオブジェクトへの参照が返されます。

3
user5319825

Python 3.8: https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-python-behavior

コンパイラは、特定の種類のリテラル(文字列、整数など)でIDチェック(使用されているかどうか)が使用される場合に、SyntaxWarningを生成するようになりました。これらはCPythonで偶然に機能することがよくありますが、言語仕様では保証されていません。警告は、代わりに同等性テスト(==および!=)を使用することをユーザーに通知します。 (bpo-34850でSerhiy Storchakaによって寄贈されました。)

0
cclauss