web-dev-qa-db-ja.com

Pythonでは、2つのオブジェクトが同じになるのはいつですか?

のようだ 2 is 2および3 is 3はPythonでは常に真であり、一般に、整数への参照は同じ整数への他の参照と同じです。同じことがNoneにも起こります(つまり、None is None)。私はこれがnotユーザー定義型または可変型に起こることを知っています。ただし、不変型でも失敗することがあります。

>>> () is ()
True
>>> (2,) is (2,)
False

つまり、空のタプルの2つの独立した構造は、メモリ内の同じオブジェクトへの参照を生成しますが、同一の1(不変)要素タプルの2つの独立した構造は、2つの同一のオブジェクトを作成することになります。テストしたところ、frozensetsはタプルと同じように機能します。

オブジェクトがメモリ内で複製されるのか、それとも多くの参照を持つ単一のインスタンスを持つのかを決定するのは何ですか?オブジェクトがある意味で「アトミック」であるかどうかに依存しますか?実装によって異なりますか?

36
fonini

Pythonには、インスタンスが1つしかないことを保証するタイプがいくつかあります。これらのインスタンスの例は、NoneNotImplemented、およびEllipsisです。これらは(定義上)シングルトンであり、Trueの新しいインスタンスを作成する方法がないため、_None is None_のようなものはNoneTypeを返すことが保証されています。

また、いくつかのダブルトンを供給します 1TrueFalse 2 --Trueへのすべての参照は、同じオブジェクトを指します。繰り返しますが、これはboolの新しいインスタンスを作成する方法がないためです。

上記のことはすべてpython言語によって保証されています。ただし、お気づきのとおり、再利用のためにいくつかのインスタンスを格納するいくつかのタイプ(すべて不変)があります。これは言語によって許可されていますが、さまざまな実装が、最適化戦略に応じて、この許容値を使用するかどうかを選択できます。このカテゴリに分類される例としては、小さい整数(-5-> 255)、空のTupleおよび空のfrozenset

最後に、Cpython internsの解析中の特定の不変オブジェクト...

例えばCpythonで次のスクリプトを実行すると、Trueが返されることがわかります。

_def foo():
    return (2,)

if __name__ == '__main__':
    print foo() is foo()
_

これは本当に奇妙なようです。 Cpythonが実行しているトリックは、関数fooを作成するたびに、他の単純な(不変の)リテラルを含むタプルリテラルを参照することです。このタプル(またはそれに相当するもの)を何度も作成するのではなく、pythonは一度だけ作成します。取引全体が不変であるため、そのオブジェクトが変更される危険はありません。これは大きな勝利になる可能性があります。同じタイトループが何度も呼び出されるパフォーマンスのために。小さな文字列もインターンされます。ここでの本当の勝利は辞書検索です。Pythonは(非常に高速な)ポインタ比較を実行してからハッシュの衝突をチェックするときは、より遅い文字列比較にフォールバックします。pythonの多くは辞書ルックアップに基づいているため、これは言語全体にとって大きな最適化になる可能性があります。


1私はちょうどその言葉を作り上げたかもしれません...しかし、うまくいけば、あなたはアイデアを得るでしょう...
2通常の状況では、オブジェクトがTrueへの参照であるかどうかをチェックする必要はありません必要通常は、オブジェクトが「真実」であるかどうかだけを気にします-例: _if some_instance: ..._がブランチを実行する場合。しかし、完全を期すために、ここにそれを入れました。


isは、シングルトンではないものを比較するために使用できることに注意してください。一般的な使用法の1つは、番兵値を作成することです。

_sentinel = object()
item = next(iterable, sentinel)
if items is sentinel:
   # iterable exhausted.
_

または:

__sentinel = object()
def function(a, b, none_is_ok_value_here=_sentinel):
    if none_is_ok_value_here is sentinel:
        # Treat the function as if `none_is_ok_value_here` was not provided.
_

この話の教訓は、常にあなたが何を意味するかを言うことです。値がis別の値であるかどうかを確認したい場合は、次に、is演算子を使用します。値等しい別の値(ただし、場合によっては異なる)かどうかを確認する場合は、_==_を使用します。 isと_==_の違い(およびいつどちらを使用するか)の詳細については、次のいずれかの投稿を参照してください。


補遺

これらのCPython実装の詳細について説明しましたが、これらは最適化であると主張しました。このすべての最適化から得られるものだけを測定してみるとよいでしょう(is演算子を使用するときに少し混乱が生じることを除いて)。

文字列「インターン」と辞書ルックアップ。

これは、別の文字列の代わりに同じ文字列を使用して値を検索する場合に、辞書検索がどれだけ高速になるかを確認するために実行できる小さなスクリプトです。変数名で「インターン」という用語を使用していることに注意してください。これらの値は必ずしもインターンされているわけではありません(インターンされている可能性はありますが)。私はそれを使用して、「インターン」文字列is辞書内の文字列を示しています。

_import timeit

interned = 'foo'
not_interned = (interned + ' ').strip()

assert interned is not not_interned


d = {interned: 'bar'}

print('Timings for short strings')
number = 100000000
print(timeit.timeit(
    'd[interned]',
    setup='from __main__ import interned, d',
    number=number))
print(timeit.timeit(
    'd[not_interned]',
    setup='from __main__ import not_interned, d',
    number=number))


####################################################

interned_long = interned * 100
not_interned_long = (interned_long + ' ').strip()

d[interned_long] = 'baz'

assert interned_long is not not_interned_long
print('Timings for long strings')
print(timeit.timeit(
    'd[interned_long]',
    setup='from __main__ import interned_long, d',
    number=number))
print(timeit.timeit(
    'd[not_interned_long]',
    setup='from __main__ import not_interned_long, d',
    number=number))
_

ここでの正確な値はそれほど重要ではありませんが、私のコンピューターでは、短い文字列は7分の1の部分をより速く表示します。 long文字列はほぼ2倍高速です(文字列に比較する文字が多い場合、文字列の比較に時間がかかるため)。 python3.xでの違いはそれほど顕著ではありませんが、それでも間違いなく存在します。

タプル「インターン」

これがあなたが遊ぶことができる小さなスクリプトです:

_import timeit

def foo_Tuple():
    return (2, 3, 4)

def foo_list():
    return [2, 3, 4]

assert foo_Tuple() is foo_Tuple()

number = 10000000
t_interned_Tuple = timeit.timeit('foo_Tuple()', setup='from __main__ import foo_Tuple', number=number)
t_list = (timeit.timeit('foo_list()', setup='from __main__ import foo_list', number=number))

print(t_interned_Tuple)
print(t_list)
print(t_interned_Tuple / t_list)
print('*' * 80)


def Tuple_creation(x):
    return (x,)

def list_creation(x):
    return [x]

t_create_Tuple = timeit.timeit('Tuple_creation(2)', setup='from __main__ import Tuple_creation', number=number)
t_create_list = timeit.timeit('list_creation(2)', setup='from __main__ import list_creation', number=number)
print(t_create_Tuple)
print(t_create_list)
print(t_create_Tuple / t_create_list)
_

これは時間を計るのが少し難しいです(そしてコメントでそれを計る方法についてもっと良いアイデアを取り入れることができてうれしいです)。この要点は、平均して(そして私のコンピューターでは)、タプルの作成にリストの約60%の時間がかかるということです。ただし、foo_Tuple()は、foo_list()にかかる時間の平均で約40%かかります。これは、これらのインターンから実際に少しスピードアップしたことを示しています。タプルが大きくなるにつれて、時間の節約が増えるようです(長いリストの作成には時間がかかります-タプルの「作成」は、すでに作成されているため、一定の時間がかかります)。

また、これを「インターン」と呼んでいることにも注意してください。実際にはそうではありません(少なくとも、文字列がインターンされているのと同じ意味ではありません)。この単純なスクリプトの違いを確認できます。

_def foo_Tuple():
    return (2,)

def bar_Tuple():
    return (2,)

def foo_string():
    return 'foo'

def bar_string():
    return 'foo'

print(foo_Tuple() is foo_Tuple())  # True
print(foo_Tuple() is bar_Tuple())  # False

print(foo_string() is bar_string())  # True
_

文字列が実際に「インターン」されていることがわかります。同じリテラル表記を使用した異なる呼び出しは、同じオブジェクトを返します。タプルの「インターン」は、単一の行に固有のようです。

38
mgilson

実装によって異なります。

CPythonは、いくつかの不変オブジェクトをメモリにキャッシュします。これは、1や2のような「小さい」整数(以下のコメントに記載されているように、-5から255)に当てはまります。 CPythonは、パフォーマンス上の理由からこれを行います。小さな整数はほとんどのプログラムで一般的に使用されるため、メモリを節約して1つのコピーのみを作成できます(整数は不変であるため安全です)。

これは、Noneのような「シングルトン」オブジェクトにも当てはまります。常に存在するNoneは1つだけです。

他のオブジェクト(空のタプル、()など)はシングルトンとして実装される場合とされない場合があります。

一般に、不変オブジェクトがこのように実装されるとは限らない仮定。 CPythonはパフォーマンス上の理由でそうしますが、他の実装ではそうしないかもしれませんし、CPythonは将来のある時点でそれをやめるかもしれません。 (唯一の例外はNoneである可能性があります。これは、x is Noneが一般的なPythonイディオムであり、さまざまなインタープリターとバージョンに実装される可能性が高いためです。)

通常は、isの代わりに==を使用します。 Pythonのis演算子は、変数がNoneであるかどうかを確認する場合を除いて、あまり使用されません。

21
mipadi