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つの任意のオブジェクトを比較して、それらが数値であるかどうかが事前にわからないときに同じであるかどうかを確認するより良い方法は何ですか?
これを見てください:
>>> 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の動作は未定義だと思います。 :-)
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期待される結果ではありません。なぜそれが予想されるのですか?これは、a
とb
の両方によって参照される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を使用して、整数、文字列、タプル、またはこれらのようなものの同等性をテストします。
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__
属性を使用して動作を指定できます。
ソースファイルintobject.c で確認できるように、Pythonは効率のために小さな整数をキャッシュします。小さな整数への参照を作成するたびに、新しいオブジェクトではなく、キャッシュされた小さな整数を参照しています。 257は小さな整数ではないため、別のオブジェクトとして計算されます。
そのためには、==
を使用することをお勧めします。
あなたの仮説は正しいと思います。 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
はリテラルとして扱われ、上記のすべてが異なるように扱われるようです!
Int、strings、datetimesなどの不変の値オブジェクトの場合、オブジェクトIDは特に役立ちません。平等について考える方が良いです。アイデンティティは本質的に値オブジェクトの実装の詳細です-それらは不変であるため、同じオブジェクトまたは複数のオブジェクトへの複数の参照を持つのに効果的な違いはありません。
is
is恒等式演算子(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 classNum 。 is
は、値の等価性ではなく、オブジェクトの同一性をチェックします。 PHPにもカラフルな履歴があります。===
は、PHP5のオブジェクト でのみis
として動作しますが、PHP4 では動作しません。これは、複数の言語(1つのバージョンを含む)を移動する際の痛みです。
既存の回答のいずれにも指摘されていない別の問題があります。 Pythonは、2つの不変の値をマージすることが許可されており、事前に作成された小さなint値だけがこれを実現する方法ではありません。 Pythonの実装は、これを行うために保証されることはありませんが、それらはすべて、小さなint以外のものに対しても行われます。
まず、空のTuple
、str
、および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.23e100
のfloat
値が事前に作成されていません。それで、ここで何が起こっているのでしょうか?
CPythonコンパイラは、同じコンパイル単位で、int
、float
、str
、bytes
などの既知の不変型の定数値をマージします。モジュールの場合、モジュール全体がコンパイル単位ですが、対話型インタープリターでは、各ステートメントは個別のコンパイル単位です。 c
とd
は別々のステートメントで定義されているため、それらの値はマージされません。 e
とf
は同じステートメントで定義されているため、それらの値はマージされます。
バイトコードを逆アセンブルすると、何が起こっているのかを確認できます。 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
を見てください。1
と2
があり、同じ_を共有する2つの(1, 2)
タプルがあります1
と2
が同一ではなく、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のルールを学習する価値がある場合があります。しかし、コードでそれらに依存する価値はありません。唯一の安全なルールは次のとおりです。
または、言い換えると、is
を使用して、文書化されたシングルトン(None
など)をテストするか、コード内の1か所でのみ作成されます(_sentinel = object()
イディオムなど)。
文字列でも起こります:
>>> 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)
今、それは予想外です。
ご覧ください こちら
現在の実装では、-5〜256のすべての整数に対して整数オブジェクトの配列が保持されます。その範囲でintを作成すると、実際には既存のオブジェクトへの参照が返されます。
Python 3.8: https://docs.python.org/3.8/whatsnew/3.8.html#changes-in-python-behavior
コンパイラは、特定の種類のリテラル(文字列、整数など)でIDチェック(使用されているかどうか)が使用される場合に、SyntaxWarningを生成するようになりました。これらはCPythonで偶然に機能することがよくありますが、言語仕様では保証されていません。警告は、代わりに同等性テスト(==および!=)を使用することをユーザーに通知します。 (bpo-34850でSerhiy Storchakaによって寄贈されました。)