web-dev-qa-db-ja.com

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

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

149
Greg

動的配列 です。実用的な証拠:インデックス付けは、インデックスに関係なく同じ時間(もちろん、非常に小さな差(0.0013 µsec!)で)かかります。

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

44
user395760

実際、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には、参照カウントとタイプ識別子が含まれます。したがって、全体を割り当てるのはベクトル/配列です。そのような配列がいっぱいになったときにサイズを変更するためのコードは listobject.c にあります。実際には配列を2倍にすることはありませんが、割り当てることで成長します

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

ここで、newsizeは要求されたサイズです(1つ1つextendする代わりに任意の数の要素でappendを使用できるため、必ずしもallocated + 1ではありません)。

Python FAQ もご覧ください。

200
Fred Foo

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

29
Amber

これは実装依存ですが、IIRC:

  • CPythonはポインターの配列を使用します
  • JythonはArrayListを使用します
  • IronPythonも配列を使用しているようです。 ソースコード を参照して確認できます。

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

27

ドキュメント によると、

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

22
ravi77o

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

オブジェクトCの構造をリストする

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

typedef struct {

PyObject_VAR_HEAD

PyObject ** ob_item;

Py_ssize_tが割り当てられました。

} PyListObject;

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

...

追加

リストに整数を追加します:l.append(1)。何が起こるのですか?
enter image description here

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

enter image description here

...

挿入

位置1:l.insert(1,5)に新しい整数(5)を挿入して、内部で何が起こるか見てみましょう。 enter image description here

...

ポップ

最後の要素l.pop()をポップすると、listpop()が呼び出されます。 list_resizeはlistpop()内で呼び出され、新しいサイズが割り当てられたサイズの半分より小さい場合、リストは縮小されます。 enter image description here

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

スロット3と4はまだいくつかの整数を指していることがわかりますが、重要なことはリストのサイズが3になったことです enter image description here

...

RemovePython listオブジェクトには、特定の要素を削除するメソッドがあります:l.remove(5)。 enter image description here

20
Lesya

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

一般性を失うことなく、メソッドがO(1)償却される理由を理解するために、a = 2 ^ n要素を挿入したと仮定し、テーブルを2 ^(n + 1)に倍増する必要がありますサイズ。つまり、現在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倍など)で行われる必要があることに注意してください

5
RussellStewart

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を追加しますが、メインリストは変更されています。つまり、そのリストはコピーリストとして割り当てられず、参照として割り当てられます。

1
hasib