Python(または不変のデータ型)にタプルが必要なのはなぜですか?


140

私はいくつかのPythonチュートリアル(Dive Into Pythonなど)と、Python.orgの言語リファレンスを読みました-言語にタプルが必要な理由がわかりません。

タプルにはリストやセットと比較してメソッドがありません。タプルをセットまたはリストに変換してソートできるようにする必要がある場合、そもそもタプルを使用する意味は何ですか?

不変性?

変数が最初に割り当てられたときとは異なるメモリ内の場所にある場合、なぜ誰かが気にするのですか?Pythonにおけるこの不変性のビジネス全体は、過度に強調されているようです。

C / C ++では、ポインタを割り当てて有効なメモリを指す場合、使用する前にアドレスがnullでない限り、アドレスの場所は気にしません。

その変数を参照するときはいつでも、ポインターがまだ元のアドレスを指しているかどうかを知る必要はありません。私はnullをチェックしてそれを使用する(または使用しない)だけです。

Pythonでは、文字列(またはタプル)を割り当ててxに割り当て、次に文字列を変更すると、元のオブジェクトであるかどうかを気にするのはなぜですか?変数が私のデータを指している限り、それだけが重要です。

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x それでも私が必要なデータを参照していますが、なぜそのIDが同じか異なるかを誰かが気にする必要があるのですか?


12
可変性の誤った側面に注意を払っています。「IDが同じか異なるか」は単なる副作用です。「以前に同じオブジェクトを指していた他の参照によって指されたデータが更新を反映するかどうか」は重要です。
Charles Duffy、

回答:


124
  1. 不変オブジェクトは、大幅な最適化を可能にします。これがおそらく、文字列もJavaで不変であり、Pythonとほぼ同じ時期に個別に開発され、真に機能する言語ではほぼすべてが不変である理由です。

  2. 特にPythonでは、不変のものだけがハッシュ可能です(したがって、セットのメンバー、または辞書のキー)。繰り返しますが、これは最適化をもたらしますが、「実質的」(はるかに変更可能なオブジェクトを格納するまともなハッシュテーブルを設計すること)よりもはるかに悪夢です-ハッシュするとすぐにすべてのコピーを取得するか、オブジェクトのハッシュかどうかをチェックする悪夢あなたが最後にそれを参照して以来、変わっています。

最適化問題の例:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop

11
@musicfreak、タプルの作成が同等のリストを作成するよりも7.6倍以上速いところで行った私が行った編集を参照してください。「注目すべき」の定義がない限り、これ以上「目立った違いを見たことがない」とは言えません。 "は本当に独特です...
Alex Martelli、2010

11
@musicfreakあなたは「時期尚早な最適化がすべての悪の根源である」と誤用していると思います。アプリケーションで時期尚早の最適化を行う(たとえば、「タプルはリストよりも速いので、すべてのアプリでタプルのみを使用する!」と言う)とベンチマークを行うのには大きな違いがあります。Alexのベンチマークは洞察に富んでおり、タプルの作成はリストの作成よりも速いことを知っていると、将来の最適化操作(本当に必要な場合)に役立つ可能性があります。
Virgil Dupras、2010

5
@Alex、「リストの作成」よりタプルの「作成」は本当に速いですか、それともPythonランタイムがタプルをキャッシュした結果が表示されていますか?私には後者のようです。
トリプティク2010

6
@ACoolie、それはrandom呼び出しによって完全に支配されます(それだけをやってみてください、あなたはわかります!)、それほど重要ではありません。試してみるpython -mtimeit -s "x=23" "[x,x]"と、タプルの作成とリストの作成の2〜3倍の有意義なスピードアップが見られます。
Alex Martelli、2010

9
疑問に思う方のために-リストからタプルに切り替えることで、1時間以上のデータ処理を削減できました。
Mark Ribau 2013

42

上記の回答のいずれも、タプルとリストの本当の問題を指摘していません。これは、Pythonの新しい多くの人が完全に理解していないようです。

タプルとリストはさまざまな目的で使用されます。リストには同種のデータが格納されます。次のようなリストを作成できます。

["Bob", "Joe", "John", "Sam"]

リストを正しく使用するのは、リストがすべて同種のデータ、特に人の名前であるためです。しかし、このようなリストを取ってください:

["Billy", "Bob", "Joe", 42]

そのリストは一人のフルネームと彼らの年齢です。これは1つのタイプのデータではありません。その情報を格納する正しい方法は、タプルまたはオブジェクトのいずれかです。いくつかあるとしましょう:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

タプルとリストの不変性と変異性は主な違いではありません。リストは、ファイル、名前、オブジェクトなど、同じ種類のアイテムのリストです。タプルは、さまざまなタイプのオブジェクトをグループ化したものです。それらにはさまざまな用途があり、多くのPythonプログラマーはタプルの目的をリストで乱用します。

しないでください。


編集:

このブログ投稿は、なぜこれが私よりも優れていると思うのかを説明していると思います:http : //news.e-scribe.com/397


13
私はあなたが少なくとも私に同意されていないビジョンを持っていると思います、他を知らないでください。
Stefano Borini、2010

13
私もこの回答に強く反対します。データの均質性は、リストとタプルのどちらを使用するかとはまったく関係ありません。Pythonでは、この違いを示唆するものはありません。
Glenn Maynard

14
グイドは数年前にもこの点を述べました。aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy 2010

11
Guido(Pythonの設計者)は、同種のデータに使用するリストと異種のタプルを使用することを意図していましたが、実際には、言語はこれを強制していません。したがって、私はこの解釈が他の何よりもスタイルの問題であると思います。多くの人の典型的な使用例では、リストは配列のようになり、タプルはレコードのようになる傾向があります。しかし、これが問題に適していれば、異種データのリストの使用を妨げるものではありません。Pythonの禅が言うように:実用性は純粋さを上回ります。
John Y

9
@グレン、あなたは基本的に間違っている。タプルの主な用途の1つは、関連する複数のデータを格納するための複合データ型としてです。タプルを反復処理して同じ操作の多くを実行できるという事実は、これを変更しません。(参考として、他の多くの言語のタプルには、対応するリストと同じ反復可能な機能がないことを考慮してください)
HS。

22

タプルをセットまたはリストに変換してソートできるようにする必要がある場合、そもそもタプルを使用する意味は何ですか?

この特定のケースでは、おそらく意味がありません。これはタプルの使用を検討するケースの1つではないため、これは問題ではありません。

ご指摘のとおり、タプルは不変です。不変の型を持つ理由はタプルに適用されます。

  • コピー効率:不変オブジェクトをコピーするのではなく、エイリアスを設定できます(変数を参照にバインドします)
  • 比較効率:参照によるコピーを使用している場合、コンテンツではなく場所を比較することで2つの変数を比較できます。
  • インターン:不変値のコピーを最大1つ格納する必要がある
  • 並行コード内の不変オブジェクトへのアクセスを同期する必要はありません
  • constの正確性:一部の値は変更できません。これが(私にとって)不変型の主な理由です。

特定のPython実装では、上記の機能のすべてを利用できない場合があることに注意してください。

ディクショナリキーは不変である必要があります。そうでない場合、キーオブジェクトのプロパティを変更すると、基になるデータ構造の不変条件が無効になる可能性があります。したがって、タプルはキーとして潜在的に使用できます。これはconstの正当性の結果です。

Dive Into PythonのIntroducing tuples」も参照してください。


2
id((1,2,3))== id((1,2,3))はfalseです。参照によってコピーされた保証がないため、場所を比較するだけではタプルを比較できません。
Glenn Maynard

@Glenn:「参照渡しのコピーを使用している場合」という限定的な注釈に注意してください。コーダーは独自の実装を作成できますが、タプルの参照によるコピーは、主にインタープリター/コンパイラーの問題です。私は主に==プラットフォームレベルでの実装方法について言及していました。
outis

1
@Glenn:参照によるコピーはのタプルには適用されないことにも注意してください(1,2,3) == (1,2,3)。それはもっとインターンの問題です。
アウト

私がかなりはっきり言ったように、それらが参照によってコピーされたという保証ありません。タプルはPythonではインターンされません。それは文字列の概念です。
Glenn Maynard

私が非常に明確に言ったように、私はプログラマが場所を比較することによってタプルを比較することについて話しているのではありません。参照によるコピーを保証できるプラットフォームの可能性について話しています。また、インターニングは、文字列だけでなく、あらゆる不変の型に適用できます。メインのPython実装では不変タイプをインターンできない場合がありますが、Pythonに不変タイプがあるという事実により、インターンがオプションになります。
2010

15

オブジェクトを辞書のキーとして使用したい場合があります

それだけの価値があるために、タプルは最近(2.6以上)成長しindex()count()メソッド


5
+1:辞書キーとしての可変リスト(または可変セットまたは可変辞書)は機能しません。したがって、不変のリスト(「タプル」)、固定されたセット、そして...まあ...固定された辞書が必要だと思います。
S.Lott、2010

9

同じ基本データ構造(配列)に対して2つの完全に別個の型を使用するのは扱いにくい設計であることが常にわかりますが、実際には実際の問題ではありません。(すべての言語にはイボがあり、Pythonが含まれていますが、これは重要なものではありません。)

変数が最初に割り当てられたときとは異なるメモリ内の場所にある場合、なぜ誰かが気にするのですか?Pythonにおけるこの不変性のビジネス全体は、過度に強調されているようです。

これらは異なるものです。可変性は、メモリに格納されている場所とは関係ありません。それが指すものは変更できないことを意味します。

Pythonオブジェクトは、作成後、変更可能かどうかにかかわらず、場所を変更できません。(より正確には、id()の値は変更できません。実際には同じです。)可変オブジェクトの内部ストレージは変更される可能性がありますが、これは隠された実装の詳細です。

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

これは変数を変更(「変更」)するものではありません。同じ名前で新しい変数を作成し、古い変数を破棄しています。変更操作と比較してください。

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

他の人が指摘したように、これにより、配列を辞書のキーとして、および不変性を必要とする他のデータ構造を使用することができます。

辞書のキーは完全に不変である必要はないことに注意してください。キーとして使用される部分のみが不変である必要があります。一部の用途では、これは重要な違いです。たとえば、ユーザーを表すクラスを使用して、一意のユーザー名で等価性とハッシュを比較できます。次に、クラスの他の変更可能なデータをハングさせます。「ユーザーはログインしています」など。これは等価性やハッシュに影響しないため、これをディクショナリのキーとして使用することは可能で完全に有効です。これはPythonではあまり必要ではありません。いくつかの人々が、キーは「不変」である必要があると主張しているが、それは部分的にのみ正しいので、私はそれを指摘するだけです。ただし、C ++のマップとセットでこれを何度も使用しています。


>>> a = [1,2,3] >>> id(a)3084599212L >>> a [1] = 5 >>> a [1、5、3] >>> id(a)3084599212L You '変更可能なデータ型を変更しただけなので、元の質問とは関係ありません。X = 'hello」をID(x)は12345 X = 『さようなら』 ID(x)は、それが新しいオブジェクトかではありません心配ならば65432限り、すべてのことだ、私が割り当てたデータへのxポイント、などの事項。。
pyNewGuy

4
あなたは私を助ける能力をはるかに超えて混乱しています。
Glenn Maynard

+1はサブタプルの混乱を指摘するためのもので、タプルの価値を理解する際の困難の主な原因であると思われます。
アウト

1
できれば、キーの真のルーブリックがオブジェクトがハッシュ可能かどうか(docs.python.org/glossary.html#term-hashable)であることを指摘するためのもう1つの+1です。
2010

7

グニブラーがコメントで提供したように、グイドは意見を持っていました完全に受け入れられていない/認められていないをます:「リストは同種データ用であり、タプルは異種データ用です」。もちろん、反対者の多くはこれを、リストのすべての要素が同じ型でなければならないことを意味すると解釈しました。

私はそれを別の方法で見るのが好きです。他の人が過去に持っていたのとは異なりません:

blue= 0, 0, 255
alist= ["red", "green", blue]

type(alist [1])!= type(alist [2])であっても、alistは同種と見なすことに注意してください。

要素の順序を変更でき、コードに問題がない場合(たとえば、「並べ替える必要がある」などの想定を除いて)、リストを使用する必要があります。そうでない場合(blue上記のタプルのように)、タプルを使用する必要があります。


できれば、この回答に15回投票します。これがタプルについての私の感じです。
グラントポール

6

渡されるオブジェクトが変更されないことを呼び出し元に保証するため、これらは重要です。これを行う場合:

a = [1,1,1]
doWork(a)

呼び出し元は、呼び出し後のaの値を保証しません。しかしながら、

a = (1,1,1)
doWorK(a)

これで、このコードの呼び出し側または読み取り側として、あなたはaが同じであることを知ってます。このシナリオでは、常にリストのコピーを作成してそれを渡すことができますが、今では、より意味のある言語構成を使用する代わりに、サイクルを無駄にしています。


1
これはタプルの非常に二次的な特性です。既存のリストであろうと他のクラスであろうと、関数に渡したい変更可能なオブジェクトがあり、変更されていない場合が多すぎます。Pythonには「参照によるconstパラメータ」という概念はありません(たとえば、const foo&in C ++)。タプルを使用すると便利な場合、タプルはこれを提供しますが、呼び出し元からリストを受け取った場合、それを別の場所に渡す前にタプルに変換しますか?
Glenn Maynard

私はあなたに同意します。タプルは、constキーワードをたたくのと同じではありません。私の要点は、タプルの不変性はコードの読者に追加の意味をもたらすということです。両方が機能する状況を想定し、タプルを使用して変更してはならないという予想がある場合(それも確認しながら)、読者に追加の意味を追加します
Matthew Manela

a = [1,1,1] doWork(a)dowork()がdef dowork(arg)として定義されている場合:arg = [0,0,0]リストまたはタプルでdowork()を呼び出すと、同じ結果
pyNewGuy


1

質問(およびフォローアップコメント)は、割り当て中にid()が変更されるかどうかに焦点を当てています。不変のオブジェクトの置き換えと変更可能なオブジェクトの変更の違いという、それ自体の違いではなく、この後続の影響に焦点を当てることは、おそらく最善のアプローチではありません。

先に進む前に、以下に示す動作がPythonに期待する動作であることを確認してください。

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

この場合、a1にのみ新しい値が割り当てられていても、a2の内容は変更されました。次とは対照的です。

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

後者の場合、リストの内容を更新するのではなく、リスト全体を置き換えました。タプルなどの不変タイプでは、これが許可される唯一の動作です。

なぜこれが問題なのですか?あなたが口述を持っているとしましょう:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

タプルを使用すると、ディクショナリのキーが「その下から」変更されて、別の値にハッシュされるアイテムに変更されることはありません。これは、効率的な実装を可能にするために重要です。


他の人が指摘したように、不変性!=ハッシュ可能性。すべてのタプルをディクショナリキーとして使用できるわけではありません:{([1]、[2]): 'value'}は失敗します。 value '}は問題ありません。
Ned Deily、2010

ネッド、それは本当ですが、その区別が、尋ねられている質問に密接に関連しているとは思えません。
Charles Duffy

@ K.Nicholas、ここで承認した編集により、タプルではなく整数が割り当てられるようにコードが変更されました-後のインデックス操作が失敗するため、新しい操作をテストできなかった可能性があります筆記録は実際に可能でした。正しく特定された問題、確か; 無効なソリューション。
Charles Duffy、

@MichaelPuckettIIも同様に、上記を参照してください。
Charles Duffy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.