3

元の質問:

(私の質問は Python 3.2+ に適用されますが、これが Python 2.7 以降に変更されているとは思えません。)

通常、オブジェクトを作成すると予想される式を使用するとします。例: [1,2,3]; 42; 'abc'; range(10); True; open('readme.txt'); MyClass(); lambda x : 2 * x; 等

このような 2 つの式が異なる時間に実行され、「同じ値に評価される」(つまり、同じ型を持ち、等しいと比較される) とします。Python はどのような条件下で、2 つの式が実際に 2 つの異なるオブジェクトを作成することを保証する、私が個別オブジェクトと呼ぶものを提供しますか (つまり、 2 つのオブジェクトが と にバインドされ、両方が同時にスコープ内にあると仮定すると、 として評価されます) x is y?Falsexy

可変型のオブジェクトについては、「個別のオブジェクトの保証」が保持されることを理解しています。

x = [1,2]
y = [1,2]
assert x is not y # guaranteed to pass 

strまた、特定の不変型 ( 、int) については保証が成り立たないことも知っています。他の特定の不変型 ( boolNoneType) については、逆の保証が適用されます。

x = True
y = not not x
assert x is not y # guaranteed to fail
x = 2
y = 3 - 1
assert x is not y # implementation-dependent; likely to fail in CPython
x = 1234567890
y = x + 1 - 1
assert x is not y # implementation-dependent; likely to pass in CPython

しかし、他のすべての不変型についてはどうでしょうか。

特に、異なる時期に作成された 2 つのタプルが同じ ID を持つことはありますか?

私がこれに興味を持っている理由は、グラフ内のノードを のタプルとして表しint、ドメイン モデルでは、任意の 2 つのノードが (同じ値のタプルで表されている場合でも) 区別されるようになっているためです。ノードのセットを作成する必要があります。異なる時点で作成されたタプルが個別のオブジェクトであることを Python が保証する場合、単純にサブクラス化tupleして、同一性を意味するように再定義することができます。

class DistinctTuple(tuple):
  __hash__ = tuple.__hash__
  def __eq__(self, other):
    return self is other

x = (1,2)
y = (1,2)
s = set(x,y)
assert len(s) == 1 # pass; but not what I want
x = DistinctTuple(x)
y = DistinctTuple(y)
s = set(x,y)
assert len(s) == 2 # pass; as desired

しかし、別の時点で作成されたタプルが明確であることが保証されていない場合、上記の方法は恐ろしい手法であり、ランダムに出現する可能性があり、複製や発見が非常に困難な潜在的なバグを隠してしまいます。その場合、サブクラス化は役に立ちません。実際には、余分な要素として一意の ID を各タプルに追加する必要があります。または、タプルをリストに変換することもできます。いずれにせよ、私はより多くのメモリを使用します。明らかに、元のサブクラス化ソリューションが安全でない場合を除き、これらの代替手段を使用しないことをお勧めします。

私の推測では、Python は、組み込みまたはユーザー定義の不変型に対して「個別オブジェクト保証」を提供していません。しかし、ドキュメントでそれについて明確な声明を見つけられませんでした。

更新 1:

@LuperRouch @larsmansこれまでの議論と回答に感謝します。ここに私がまだはっきりしていない最後の問題があります:

ユーザー定義型のオブジェクトを作成すると、既存のオブジェクトが再利用される可能性はありますか?

これが可能であれば、私が使用しているクラスがそのような動作を示す可能性があるかどうかを確認する方法を知りたいです。

これが私の理解です。ユーザー定義クラスのオブジェクトが作成されるときはいつでも、クラスの__new__()メソッドが最初に呼び出されます。このメソッドがオーバーライドされた場合、プログラマーが既存のオブジェクトへの参照を返すことを妨げる言語は何もないため、「個別のオブジェクトの保証」に違反します。明らかに、クラス定義を調べることでそれを観察できます。

__new__()ユーザー定義のクラスがオーバーライドされない場合(または明示的__new__()に基本クラスに依存している場合) はどうなるかわかりません。私が書いたら

class MyInt(int):
  pass

オブジェクトの作成は によって処理されint.__new__()ます。これは、次のアサーションが失敗する場合があることを意味すると思います。

x = MyInt(1)
y = MyInt(1)
assert x is not y # may fail, since int.__new__() might return the same object twice?

しかし、私の CPython での実験では、そのような動作を実現できませんでした。これは、言語が をオーバーライドしないユーザー定義クラスに「個別のオブジェクト保証」を提供することを意味するの__new__でしょうか、それとも単に任意の実装動作なのでしょうか?

更新 2:

DistinctTupleの実装は完全に安全であることが判明しましたが、DistinctTupleノードをモデル化するために使用するという私の設計思想が非常に悪いことがわかりました。

ID 演算子は言語で既に使用可能です。==と同じように振る舞うことは、論理的にis不必要です。

さらに悪いことに、==何か役に立つことができたとしても、それを利用できないようにしました。たとえば、プログラムのどこかで、2 つのノードが同じ整数のペアで表されているかどうかを確認したい場合がよくあります。==それには完璧だったでしょう-そして実際、それはデフォルトでそれが行うことです...

さらに悪いことに、ほとんどの人は==、ユーザー定義のクラスであっても、ID ではなく「値」を比較することを実際に期待しています。身元だけを見る私のオーバーライドでは、彼らは気づかないうちに捕まってしまうでしょう。

最後に...再定義しなければならなかった唯一の理由は==、同じタプル表現を持つ複数のノードをセットの一部にできるようにすることでした。これは間違った方法です。==変更する必要があるのは動作ではなく、コンテナーの種類です! セットの代わりにマルチセットを使用する必要がありました。

要するに、私の質問は他の状況ではある程度の価値があるかもしれませんが、作成は私のユースケースにとってひどい考えであると確信してclass DistinctTupleいます (そして、有効なユースケースがまったくないのではないかと強く思っています)。

4

3 に答える 3

4

Python リファレンス、セクション 3、データ モデル:

不変型の場合、新しい値を計算する操作は、同じ型と値を持つ既存のオブジェクトへの参照を実際に返す可能性がありますが、可変オブジェクトの場合、これは許可されていません

(強調を追加しました。)

実際には、CPython は空のタプルのみをキャッシュしているようです。

>>> 1 is 1
True
>>> (1,) is (1,)
False
>>> () is ()
True
于 2012-04-17T10:16:15.047 に答える
3

ユーザー定義型のオブジェクトを作成すると、既存のオブジェクトが再利用される可能性はありますか?

これは、ユーザー定義型がそれを行うように明示的に設計されている場合にのみ発生します。またはいくつ__new__()かのメタクラス。

使用しているクラスについて、そのような動作を示す可能性があるかどうかを確認する方法を知りたいです。

ソース、ルークを使用してください。

に関してはint、小さい整数が事前に割り当てられており、これらの事前に割り当てられた整数は、整数を使用して計算を作成する場合は常に使用されます。MyInt(1) is MyInt(1)あなたが持っているものは整数ではないので、あなたがそうするとき、あなたはこれを機能させることができません。でも:

>>> MyInt(1) + MyInt(1) is 2
True

これはもちろん、MyInt(1)+ MyInt(1)がMyIntを返さないためです。これはintを返します。これは__add__、整数のが返されるものであるためです(また、事前に割り当てられた整数のチェックも行われます)。これは、intのサブクラス化が一般的に特に有用ではないことを示しているだけです。:-)

これは、言語がnewをオーバーライドしないユーザー定義クラスに「個別のオブジェクト保証」を提供することを意味しますか、それとも単なる任意の実装動作ですか?

そうする必要がないので、それはそれを保証しません。デフォルトの動作は、新しいオブジェクトを作成することです。それを望まない場合は、それをオーバーライドする必要があります。保証があるのは意味がありません。

于 2012-04-18T04:01:00.983 に答える
1

異なる時点で作成されたタプルが個別のオブジェクトであることを Python が保証するのであれば、単純にサブクラス化tupleして、同一性を意味するように再定義することができます。

BサブクラスABがどのように機能するかについて混乱してAいるAようBですA。これは次の場合にも当てはまります__new__

--> class Node(tuple):
...   def __new__(cls):
...     obj = tuple.__new__(cls)
...     print(type(obj))
...     return obj
...
--> n = Node()
<class '__main__.Node'>

@larsman がPython リファレンスで指摘したように:

不変型の場合、新しい値を計算する操作は、同じ型と値を持つ既存のオブジェクトへの参照を実際に返す可能性がありますが、可変オブジェクトの場合、これは許可されていません

ただし、この文章は Python の組み込み型について話しているのであって、ユーザー定義型ではありません (好きなように夢中になる可能性があります)。


上記の抜粋は、Python が既存のオブジェクトと同じ新しい可変オブジェクトを返さないことを保証するものであり、Python コードで作成されたユーザー定義のクラスは本質的に可変であることを理解しています (繰り返しますが、クレイジーなユーザーに関する上記の注を参照してください。定義されたクラス)。

より完全な Node クラス (明示的に参照する必要がないことに注意してくださいtuple.__hash__):

class Node(tuple):
    __slots__ = tuple()
    __hash__ = tuple.__hash__
    def __eq__(self, other):
        return self is other
    def __ne__(self, other):
        return self is not other

--> n1 = Node()
--> n2 = Node()
--> n1 is n2
False
--> n1 == n2
False
--> n1 != n2
True

--> n1 <= n2
True
--> n1 < n2
False

最後の 2 つの比較からわかるように、 メソッド__le____ge__メソッドもオーバーライドしたい場合があります。

[1] 私が認識している唯一の例外は__hash__--__eq__がサブクラスで定義されているが、サブクラスが親クラス__hash__を必要としている場合は、明示的にそのように言わなければなりません (これは Python 3 の変更です)。

于 2012-04-18T14:15:20.107 に答える