回答:
それは素晴らしい質問です。
重要な洞察は、タプルには内部のオブジェクトが変更可能かどうかを知る方法がないということです。オブジェクトを変更可能にする唯一のことは、そのデータを変更するメソッドを持つことです。一般に、これを検出する方法はありません。
別の洞察は、Pythonのコンテナーには実際には何も含まれていないということです。代わりに、他のオブジェクトへの参照を保持します。同様に、Pythonの変数はコンパイル済み言語の変数とは異なります。代わりに、変数名は、対応するオブジェクトに関連付けられている名前空間ディクショナリの単なるキーです。Ned Batchhelderは彼のブログ投稿でこれをうまく説明しています。どちらの方法でも、オブジェクトは参照カウントしか知りません。それらはそれらの参照が何であるか(変数、コンテナ、またはPythonの内部)を知りません。
これら2つの洞察が一緒になって、あなたの謎が説明されます(リストが「含まれている」不変のタプルが、基になるリストが変更されると変更されるように見える理由)。実際、タプルは変更されませんでした(他のオブジェクトへの参照は以前と同じです)。タプルは変更できませんでした(変更メソッドがなかったため)。リストが変更されたとき、タプルは変更の通知を受けませんでした(リストは、それが変数、タプル、または別のリストによって参照されているかどうかを知りません)。
私たちがトピックに取り組んでいる間、タプルとは何か、それらがどのように機能するか、およびそれらの使用目的についてのメンタルモデルを完成させるのに役立つ他のいくつかの考えを次に示します。
タプルの特徴は、不変性ではなく、意図した目的にあります。
タプルは、1つの屋根の下で異種の情報を収集するPythonの方法です。たとえば
s = ('www.python.org', 80)
、ホストとポートのペアをソケット、複合オブジェクトとして渡すことができるように、文字列と数値を結合します。その観点から見ると、変更可能なコンポーネントを持つことは完全に合理的です。
不変性は、別のプロパティであるハッシュ可能性と密接に関連しています。しかし、ハッシュ可能性は絶対的な特性ではありません。タプルのコンポーネントの1つがハッシュ可能でない場合、タプル全体もハッシュ可能ではありません。たとえば、t = ('red', [10, 20, 30])
ハッシュ可能ではありません。
最後の例は、文字列とリストを含む2タプルを示しています。タプル自体は変更できません(つまり、タプルの内容を変更するメソッドはありません)。同様に、文字列には変更メソッドがないため、文字列は不変です。リストオブジェクトには変更メソッドがあるため、変更できます。これは、可変性がオブジェクトタイプのプロパティであることを示しています。一部のオブジェクトには変更メソッドがあり、一部のオブジェクトにはありません。オブジェクトがネストされているからといって、これは変わりません。
2つのことを覚えておいてください。第一に、不変性は魔法ではありません。それは、単にメソッドを変更しないことです。第二に、オブジェクトはどの変数またはコンテナがそれらを参照しているかを知りません-それらは参照カウントしか知りません。
よろしくお願いします:-)
hash()
から継承するものはすべてハッシュ可能であり、サブクラスが明示的にハッシュをオフにする必要があるため、呼び出しなしでハッシュ可能性を検出することは簡単ではありません。2)ハッシュ可能性は不変性を保証するものではありません-変更可能なハッシュ可能なオブジェクトの例を作るのは簡単です。3)タプルは、Pythonのほとんどのコンテナーと同様に、基礎となるオブジェクトへの参照のみを持っています。
これは、タプルにリスト、文字列、数値が含まれていないためです。他のオブジェクトへの参照が含まれています。1タプルに含まれる参照のシーケンスを変更できないことは、それらの参照に関連付けられているオブジェクトを変更できないことを意味しません。2
1. オブジェクト、値、および型(2番目から最後の段落を参照)
2 .標準の型階層(「Immutableシーケンス」を参照)
まず、「不変」という言葉は、人によってさまざまな意味を持ちます。Eric Lippertが彼のブログ投稿で不変性を分類した方法が特に好きです。そこで、彼はこれらの種類の不変性をリストします:
これらはさまざまな方法で組み合わせることができ、さらに多くの種類の不変性を作成できます。不変のオブジェクトは他の不変のオブジェクトのみを含むことができる、深い(推移的とも呼ばれる)不変性に興味があると思われる種類の不変性。
この重要な点は、深い不変性は多くの多くの種類の不変性の1つにすぎないということです。「不変」の概念がおそらく他の人の「不変」の概念と異なる可能性があることを認識している限り、どのような種類のものでも採用できます。
私が理解しているように、この質問は、設計の決定に関する質問として言い換える必要があります。なぜPythonの設計者は、可変オブジェクトを含むことができる不変シーケンス型を作成することを選んだのですか?
この質問に答えるには、タプルが果たす目的について考える必要があります。タプルは高速で汎用的なシーケンスとして機能します。これを念頭に置くと、タプルが不変であるのに、変更可能なオブジェクトを含めることができる理由が明らかになります。ウィットするには:
タプルは高速でメモリ効率が良い:タプルは不変であるため、リストよりも作成が高速です。不変性とは、タプルを定数として作成し、定数フォールディングを使用してそのようにロードできることを意味します。また、割り当て超過などの必要がないため、作成が高速でメモリ効率が高いことも意味します。ランダムなアイテムへのアクセスはリストよりも少し遅くなりますが、(少なくとも私のマシンでは)開梱は再び速くなります。タプルが変更可能である場合、タプルはこれらのような目的にはそれほど速くありません。
タプルは汎用です:タプルはあらゆる種類のオブジェクトを含むことができる必要があります。これらは、(関数定義の演算子を介して)可変長引数リストのようなことを(すばやく)行うために使用されます*
。タプルが可変オブジェクトを保持できない場合、タプルはこのようなことには役に立たないでしょう。Pythonはリストを使用する必要があります。これにより、処理が遅くなり、メモリ効率が低下します。
つまり、タプルは目的を達成するために不変でなければならず、可変オブジェクトを含めることができなければなりません。Pythonの設計者が、「含まれる」すべてのオブジェクトも不変であることを保証する不変オブジェクトを作成したい場合、3番目のシーケンス型を作成する必要があります。利益は余分な複雑さの価値がありません。
ここで手足を伸ばして、ここで関連する部分は、タプル内に含まれているリストの内容またはオブジェクトの状態を変更できる一方で、変更できないのはオブジェクトであるということです。またはリストがあります。空であっても、thing [3]がリストであることに依存しているものがあれば、これが役立つと思います。
1つの理由は、Pythonで可変型を不変型に変換する一般的な方法がないためです(拒否されたPEP 351と、拒否された理由についてのリンクされた説明を参照してください)。したがって、この制限がある場合、ユーザーが作成したハッシュ化できないオブジェクトなど、さまざまなタイプのオブジェクトをタプルに配置することは不可能です。
辞書とセットにこの制限がある唯一の理由は、ハッシュテーブルとして内部的に実装されているため、オブジェクトをハッシュ可能にする必要があるためです。ただし、皮肉なことに、辞書とセット自体は不変(またはハッシュ可能)ではありません。タプルはオブジェクトのハッシュを使用しないため、その可変性は重要ではありません。