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