web-dev-qa-db-ja.com

PythonクラスのIDがすばやく呼び出されたときに一意ではないのはなぜですか?

Python(3.3.3))でいくつかのことを行っていますが、クラスが呼び出されるたびにクラスが新しいIDを取得するため、混乱を招くような問題に遭遇しました。

いくつかの.pyファイルにこれがあるとしましょう:

class someClass: pass

print(someClass())
print(someClass())

上記は同じIDを返すので、呼び出しを行っているので混乱します。同じにしないでください。 Pythonは、同じクラスが続けて2回呼び出されたときに機能しますか?数秒待ったときに上記の例のように同じように実行すると、異なるIDが得られますそのようには動作しないようで、混乱しています。

>>> print(someClass());print(someClass())
<__main__.someClass object at 0x0000000002D96F98>
<__main__.someClass object at 0x0000000002D96F98>

同じことを返しますが、なぜですか?私は例えば範囲でそれにも気づきます

for i in range(10):
    print(someClass())

Pythonクラスがすぐに呼び出されたときにこれを行う特別な理由はありますか?私は知らなかったPythonこれを実行したか、それともバグですか? ?それがバグではない場合、誰かがそれを修正する方法またはメソッドを説明して、メソッド/クラスが呼び出されるたびに異なるIDを生成するように誰かに説明できますか?待機しているため、同じクラスを2回以上呼び出そうとしても変わりません。

33
user3130555

オブジェクトのidは、プログラムの存続期間全体ではなく、一意であることだけが保証されていますそのオブジェクトの存続期間中。作成した2つのsomeClassオブジェクトは、printを呼び出している間だけ存在します-その後、ガベージコレクションに使用できます(CPythonでは、すぐに割り当て解除されます)。ライフタイムは重複しないため、IDを共有することは有効です。

2つのCPython実装の詳細の組み合わせのため、この場合も驚くべきことではありません。1つ目は、参照カウントによるガベージコレクション(循環参照に関する問題を回避するための特別な魔法を使用)、2つ目は、idです。オブジェクトの値は、変数の基になるポインタの値(つまり、そのメモリ位置)に関連しています。したがって、割り当てられた最新のオブジェクトであった最初のオブジェクトはすぐに解放されます-割り当てられたnextオブジェクトが同じ場所に配置されることはそれほど驚くことではありません(ただし、これはインタプリタのコンパイル方法の詳細)。

異なるidsを持つ複数のオブジェクトに依存している場合は、それらを保持して、たとえばリストにして、それらのライフタイムが重複するようにすることができます。それ以外の場合は、保証が異なるクラス固有のIDを実装する可能性があります-例:

class SomeClass:
    next_id = 0

    def __init__(self):
         self.id = SomeClass.nextid
         SomeClass.nextid += 1
36
lvc

id のドキュメントを読むと、次のようになります。

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

そして、それがまさに何が起こっているかです。2つ目のオブジェクトが作成される前に、最初のオブジェクトはすでにスコープ外にあるため、ライフタイムが重複しない2つのオブジェクトがあります。


しかし、これがalwaysになることも信用しないでください。特に、他のPython実装、またはより複雑なクラスを処理する必要がある場合。言語が言うことは、これら2つのオブジェクトmayが同じid()の値ではなく、それらがwillであること、そしてdoであるという事実は、2つの実装の詳細に依存します。

  • ガベージコレクターは、コードが2番目のオブジェクトの割り当てを開始する前に、最初のオブジェクトをクリーンアップする必要があります。これは、CPythonまたはその他の参照カウント実装(循環参照がない場合)で発生することが保証されていますが、世代別オブジェクトではほとんどありません。 JythonまたはIronPythonのようなガベージコレクタ。

  • カバーの下のアロケーターは、同じタイプの最近解放されたオブジェクトを再利用することを非常に強く優先する必要があります。これは、基本的なC mallocの上に複数のファンシーアロケーター層があるCPythonにも当てはまりますが、他のほとんどの実装では、基盤となる仮想マシンに多くのことを任せています。


最後にもう1つ:_object.__repr___にたまたまidと同じ部分文字列が16進数として含まれているという事実は、CPythonの実装アーティファクトであり、どこでも保証されていません。 ドキュメント によると:

可能な場合は、これは有効なPython式を使用して、適切な環境で同じ値のオブジェクトを再作成するために使用できます)のようになります。これが不可能な場合は、 _<...some useful description…>_という形式が返されます。

CPythonのobjectがたまたまhex(id(self))を置くという事実(実際には、sprintf-%p_を介してポインターを実行するのと同等のことをしていると思いますが、CPythonのidlongにキャストされた同じポインタを返すだけで、最終的には同じになります)はどこでも保証されません。それが真実だったとしても…objectの前は2.xの初期に存在していました。対話型プロンプトでのこの種の単純な「ここで何が起こっているのか」のデバッグはこれに依存しても安全ですが、それを超えてそれを使用しないでください。

14
abarnert

ここでより深い問題を感じます。プログラムの存続期間中、一意のインスタンスを追跡するためにidに依存するべきではありません。これは、各オブジェクトインスタンスの期間中、非保証型のメモリロケーションインジケータとして表示されるだけです。すぐにインスタンスを作成して解放すると、同じメモリ位置に連続したインスタンスを作成することができます。

おそらく、新しいインスタンスごとに一意のIDを割り当て、次のインスタンスのクラス静的カウンターをインクリメントするクラス静的カウンターを追跡する必要があるかもしれません。

4
Preet Kukreti

それは保持されなかったので最初のインスタンスを解放しています。それまではメモリに何も起こらなかったため、同じ場所に2回目のインスタンスを作成します。

3
mhlester

これを試して、次を呼び出してみてください:

a = someClass()
for i in range(0,44):
    print(someClass())
print(a)

別の何かが表示されます。どうして? 「foo」ループの最初のオブジェクトによって解放されたメモリが再利用されました。一方、aは保持されるため再利用されません。

3
wheaties