オブジェクトのインスタンスの__class__属性を変更できないのはなぜですか?


10
class A(object):
    pass

class B(A):
    pass

o = object()
a = A()
b = B()

を変更a.__class__することはできますが、同じことはできませんo.__class__TypeErrorエラーが発生します)。どうして?

例えば:

isinstance(a, A) # True
isinstance(a, B) # False
a.__class__ = B
isinstance(a, A) # True
isinstance(a, B) # True

isinstance(o, object) # True
isinstance(o, A) # False
o.__class__ = A # This fails and throws a TypeError
# isinstance(o, object)
# isinstance(o, A)

正しく処理しないと、非常に奇妙な動作が発生する可能性があるため、これは一般に良い考えではありません。それは好奇心のためだけです。


3
組み込み型は、効率上の理由からユーザー定義型のダイナミズムを犠牲にします。もう1つのオプションの最適化はスロットであり、これは同様にこれを防ぎます。
juanpa.arrivillaga

回答:


6

CPythonはこのトピックに関するObjects / typeobject.cにコメントがあります。

3.5より前のバージョンのCPythonでは、compatible_for_assignmentHEAPTYPE以外のクラスのメモリレイアウトやスロットなどの互換性を正しくチェックするようにのコード が設定されていなかったため、__class__HEAPTYPE-> HEAPTYPE以外の場合は割り当てを許可しませんでした。

3.5開発サイクル中に、compatible_for_assignment任意のタイプ間の互換性を正しくチェックするようにコードを修正し __class__、古いタイプと新しいタイプが実際に互換性のあるスロットとメモリレイアウトを持っているすべてのケースで割り当てを許可し始めました(それらがHEAPTYPEとして実装されているかどうかに関係なく)か否か)。

しかし、3.5がリリースされる直前に、インタプリタが不変であると想定し、いくつかの値をインターンするというintのような不変の型で問題が発生することを発見しました。以前はこれは問題ではありませんでした。それらは本当に不変だったためです。特に、インタープリターがこのインターントリックを適用したすべてのタイプは静的に割り当てられていたため、古いHEAPTYPEルールによって「誤って」__class__割り当てが許可されていませんでした。しかし、__class__割り当ての変更により、次のようなコードを許可し始めました

class MyInt(int):
#   ...
# Modifies the type of *all* instances of 1 in the whole program,
# including future instances (!), because the 1 object is interned.
 (1).__class__ = MyInt

https://bugs.python.org/issue24912を参照してください)。

理論的には、適切な修正は、どのクラスがこの不変式に依存しているかを特定__class__し、おそらく新しいPy_TPFLAGS_IMMUTABLEフラグ(「ブラックリスト」アプローチ)などのメカニズムを介して、それらだけに割り当てを許可しないことです。しかし実際には、この問題は3.5 RCサイクルの後半には気付かなかったため、保守的なアプローチを採用し、以前と同じHEAPTYPE-> HEAPTYPEチェックと「ホワイトリスト」を復活させています。今のところ、ホワイトリストはModuleTypeサブタイプのみで構成されています。これは、そもそもパッチの動機となったケースであるためです。https://bugs.python.org/issue22986を参照してください。モジュールオブジェクトは変更可能であるため、確実に確認できます。彼らは絶対に抑留されていないこと。したがって、HEAPTYPE-> HEAPTYPE または ModuleTypeサブタイプ-> ModuleTypeサブタイプ。

私たちが知る限り、次の 'if'ステートメントを超えるすべてのコードは非HEAPTYPEクラスを正しく処理し、HEAPTYPEチェックは、インタープリターが以下の前提でベイクした非HEAPTYPEクラスのサブセットを保護するためにのみ必要ですすべてのインスタンスは真に不変です。

説明:

CPythonは2つの方法でオブジェクトを保存します。

オブジェクトは、ヒープに割り当てられた構造です。オブジェクトが適切にガベージコレクションされるようにするため、オブジェクトの使用には特別なルールが適用されます。オブジェクトが静的またはスタックに割り当てられることはありません。特別なマクロと関数を介してのみアクセスする必要があります。 (タイプオブジェクトは最初のルールの例外です。標準タイプは静的に初期化されたタイプオブジェクトで表されますが、Python 2.2のタイプ/クラス統合での作業により、ヒープに割り当てられたタイプオブジェクトも使用できるようになりました)。

Include / object.hのコメントからの情報。

新しい値をに設定しようとするsome_obj.__class__と、object_set_class関数が呼び出されます。PyBaseObject_Typeから継承されてい/* tp_getset */ます。フィールドを参照してください。この関数は以下をチェックします。新しいタイプは古いタイプを置き換えることができsome_objますか?

あなたの例を見てみましょう:

class A:
    pass

class B:
    pass

o = object()
a = A() 
b = B() 

最初のケース:

a.__class__ = B 

aオブジェクトのタイプはA動的に割り当てられるため、ヒープタイプです。同様にBa型は問題なく変更されます。

2番目のケース:

o.__class__ = B

のタイプoは組み込みタイプobjectPyBaseObject_Type)です。これはヒープタイプではないため、TypeError発生します。

TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.

4

__class__同じ内部(C)レイアウトを持つ別のタイプにのみ変更できます。タイプ自体が動的に割り当てられない限り(「ヒープタイプ」)、ランタイムはそのレイアウトすら知りません。これは、組み込みタイプをソースまたは宛先として除外する必要条件です。また__slots__、同じ名前の同じセットが必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.