Pythonのリストはどのように実装されていますか?


182

リンクされたリストですか、配列ですか?探し回ったところ、推測している人しか見つかりませんでした。私のCの知識は、ソースコードを見るには十分ではありません。

回答:


57

これは動的配列です。実用的な証明:インデックス作成は、インデックスに関係なく(もちろん、ごくわずかな差異(0.0013 µ秒!))同じ時間を要します。

...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop

...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop

IronPythonまたはJythonがリンクされたリストを使用する場合、私は驚かれます-リストは動的配列であるという仮定に基づいて構築された多くの広く使用されているライブラリのパフォーマンスを台無しにします。


1
@Ralf:私のCPU(他のほとんどのハードウェアも同様)が古くて遅いことを知っています-明るい面では、私にとって十分に高速で実行されるコードは、すべてのユーザーにとって十分に高速であると想定できます:D

88
@delnan:-1 6つの賛成票と同様に、「実用的な証明」はナンセンスです。時間の約98%がに費やされているためx=[None]*1000、起こり得るリストアクセスの違いの測定値は不正確なままです。あなたは初期化を分離する必要があります:-s "x=[None]*100" "x[0]"
ジョン・マチン

26
リンクリストの単純な実装ではないことを示します。それが配列であることを明確に示していません。
Michael Mior

6
あなたはそれについてここで読むことができます:docs.python.org/2/faq/design.html#how-are-lists-implemented
CCoder

3
単にリンクされたリストと配列よりもはるかに多くの構造があり、タイミングはそれらの間で決定するための実用的ではありません。
ロスヘムズリー2014

236

Cコードは実際にはかなり単純です。1つのマクロを展開し、無関係なコメントを削除すると、基本的な構造はになりlistobject.h、リストは次のように定義されます。

typedef struct {
    PyObject_HEAD
    Py_ssize_t ob_size;

    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     */
    Py_ssize_t allocated;
} PyListObject;

PyObject_HEAD参照カウントとタイプ識別子が含まれています。だから、それはoverococateするベクトル/配列です。このような配列がいっぱいになったときにサイズを変更するコードはにありlistobject.cます。実際には配列を2倍にするわけではありませんが、

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;

毎回容量に、newsizeは要求されたサイズです(要素を1つずつではなく、任意の数の要素を使用allocated + 1できるためとは限りません)。extendappend

Python FAQも参照してください


6
したがって、すべてのエントリは単なるポインタであり、すべての要素がキャッシュミスを引き起こす可能性が最も高いため、Pythonリストを反復処理するときは、リンクリストと同じくらい低速です。
Kr0e 14

9
@ Kr0e:後続の要素が実際に同じオブジェクトの場合はそうではありません:)しかし、より小さく/キャッシュにやさしいデータ構造が必要な場合は、arrayモジュールまたはNumPyが推奨されます。
Fred Foo

@ Kr0eリストの反復はリンクリストほど遅くはありませんが、リンクリストのの反復はリンクリストと同じくらい遅く、フレッドが指摘した警告があります。たとえば、リストを反復して別のリストにコピーする方が、リンクリストよりも高速です。
Ganea Dan Andrei

35

CPythonでは、リストはポインターの配列です。Pythonの他の実装では、さまざまな方法でそれらを格納することを選択できます。


32

これは実装に依存しますが、IIRC:

  • CPythonはポインターの配列を使用します
  • Jythonは、 ArrayList
  • IronPythonも配列を使用しているようです。ソースコードを閲覧して調べることができます。

したがって、それらはすべてO(1)ランダムアクセスを持っています。


1
リンクリストとしてリストを実装したpythonインタープリターのように実装に依存するのは、python言語の有効な実装でしょうか?つまり、O(1)リストへのランダムアクセスは保証されていません。実装の詳細に依存せずに効率的なコードを書くことは不可能ではありませんか?
sepp2k

2
@sepp Pythonのリストは順序付けられたコレクションだと思います。上記の実装の実装および/またはパフォーマンス要件は明示的に述べられていない
NullUserException

6
@ sppe2k:Pythonには実際に標準または正式な仕様がないため(「...が保証される」というドキュメントはいくつかあります)、「これは100%確実ではありません。紙によって保証されています。」しかし、O(1)リストのインデックス作成はかなり一般的で有効な前提であるため、あえてそれを壊すような実装はありません。

@Paulリストの基礎となる実装がどのように行われるべきかについては何も述べていません。
NullUserException 2010年

ビッグOの実行時間を指定することはありません。言語構文の仕様は、必ずしも実装の詳細と同じことを意味するわけではなく、たまたまそうなのです。
ポールマクミラン

26

Laurent Luceの記事「Pythonリストの実装」をお勧めします。著者がリストがCPythonでどのように実装されているかを説明し、この目的のために優れた図を使用しているので、私にとって本当に役に立ちました。

オブジェクトC構造体のリスト

CPythonのリストオブジェクトは、次のC構造体で表されます。ob_itemリスト要素へのポインタのリストです。allocationは、メモリに割り当てられたスロットの数です。

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

割り当てられたスロットとリストのサイズの違いに注意することが重要です。リストのサイズはと同じlen(l)です。割り当てられたスロットの数は、メモリに割り当てられた数です。多くの場合、割り当てられたサイズよりも大きいことがわかります。これはrealloc、新しい要素がリストに追加されるたびに呼び出す必要がないようにするためです。

...

追加

リストに整数を追加しますl.append(1)。何が起こるのですか?
ここに画像の説明を入力してください

続いて、もう1つの要素を追加しますl.append(2)list_resizen + 1 = 2で呼び出されますが、割り当てられたサイズが4であるため、さらにメモリを割り当てる必要はありません。我々は2つの以上の整数を追加するときに同じことが起こります:l.append(3)l.append(4)。次の図は、これまでのところを示しています。

ここに画像の説明を入力してください

...

インサート

位置1に新しい整数(5)を挿入して、l.insert(1,5)内部で何が起こるかを見てみましょう。ここに画像の説明を入力してください

...

ポップ

最後の要素をポップするとl.pop()listpop()が呼び出されます。list_resize内部で呼び出されlistpop()、新しいサイズが割り当てられたサイズの半分未満の場合、リストは縮小されます。ここに画像の説明を入力してください

スロット4がまだ整数を指していることを確認できますが、重要なのは、現在は4であるリストのサイズです。要素をもう1つポップしましょう。ではlist_resize()、サイズ– 1 = 4 – 1 = 3は割り当てられたスロットの半分未満なので、リストは6スロットに縮小され、リストの新しいサイズは3になります。

スロット3と4がまだいくつかの整数をポイントしていることを確認できますが、重要なのは、現在は3であるリストのサイズです。ここに画像の説明を入力してください

...

削除 Pythonのリストオブジェクトは、特定の要素を削除する方法がありますl.remove(5)ここに画像の説明を入力してください


おかげで、私はリストのリンク部分をさらに理解しました。Pythonリストはでaggregationはなく、compositionです。作曲のリストもあったらいいのに。
shuva 2018

22

ドキュメントによると、

Pythonのリストは、実際には可変長配列であり、Lispスタイルのリンクリストではありません。


5

他の人が上で述べたように、リストは(かなり大きい場合)固定量のスペースを割り当てて実装され、そのスペースがいっぱいになる場合は、より多くのスペースを割り当てて要素をコピーします。

メソッドが一般性を失うことなくO(1)で償却される理由を理解するために、a = 2 ^ n要素を挿入し、テーブルを2 ^(n + 1)サイズに2倍にする必要があると仮定します。つまり、現在2 ^(n + 1)演算を実行しています。最後のコピーでは、2 ^ n回の操作を行いました。その前に、2 ^(n-1)... 8、4、2、1までずっと計算しました。これらを合計すると、1 + 2 + 4 + 8 + ... + 2 ^(n + 1)= 2 ^(n + 2)-1 <4 * 2 ^ n = O(2 ^ n)= O(a)合計挿入数(つまり、O(1)償却時間)。また、テーブルで削除が許可されている場合、テーブルの縮小は別の係数(3倍など)で行う必要があることに注意してください。


私の知る限り、古い要素のコピーはありません。より多くのスペースが割り当てられますが、新しいスペースは既に使用されているスペースと隣接しておらず、挿入される新しい要素のみが新しいスペースにコピーされます。私が間違っていたら訂正してください。
Tushar Vazirani

1

Pythonのリストは、複数の値を格納できる配列のようなものです。リストは変更可能であるため、変更できます。さらに重要なことは、リストを作成すると、Pythonはそのリスト変数のreference_idを自動的に作成するということです。他の変数を割り当てて変更すると、メインリストが変更されます。例で試してみましょう:

list_one = [1,2,3,4]

my_list = list_one

#my_list: [1,2,3,4]

my_list.append("new")

#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']

追加しましたmy_listが、メインリストが変更されました。つまり、リストは参照として割り当てられたコピーリストとして割り当てられませんでした。


0

CPythonのリストでは動的配列として実装されているため、そのときに追加すると、1つのマクロが追加されるだけでなく、新しいスペースが追加されないように、いくつかのスペースが割り当てられます。

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