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