파이썬의 목록은 어떻게 구현됩니까?


183

연결된 목록입니까, 배열입니까? 나는 주변을 수색하고 사람들 만 추측하는 것을 발견했다. 내 C 지식은 소스 코드를 볼만큼 충분하지 않습니다.

답변:


58

그것은의 동적 배열 . 실용적 증거 : 색인에 상관없이 색인 생성에는 시간이 많이 걸립니다 (물론 아주 작은 차이 (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에서 링크 된 목록을 사용하면 놀라 울 것입니다. 목록이 동적 배열이라는 가정하에 널리 사용되는 많은 라이브러리의 성능을 망칠 것입니다.


1
@Ralf는 : - 나는 (그 문제에 너무 다른 대부분의 하드웨어) 내 CPU를 알고 천천히 세 개입니다 밝은 측면에서, 나는 나를 위해 충분히 빨리 실행하는 모든 사용자에 대해 충분히 빨리는 것을 그 코드를 가정 할 수있다 : D를

88
@delnan : -1 당신의 "실용적 증거"는 6 개의 공감대와 마찬가지로 말도 안됩니다. 시간의 약 98 %가 수행되어 x=[None]*1000가능한 목록 액세스 차이의 측정이 다소 부정확합니다. 초기화를 분리해야합니다.-s "x=[None]*100" "x[0]"
— John Machin

26
연결된 목록의 순진한 구현이 아님을 나타냅니다. 그것이 배열임을 확실하게 보여주지는 않습니다.
— Michael Mior

6
여기에서 읽을 수 있습니다 : docs.python.org/2/faq/design.html#how-are-lists-implemented
— CCoder

3
링크 된 목록 및 배열보다 훨씬 많은 구조가 있으므로 타이밍을 결정하는 데 실용적이지 않습니다.
— 로스 Hemsley

236

실제로 C 코드는 매우 간단합니다. 하나의 매크로를 확장하고 관련이없는 주석을 정리하면 기본 구조는에 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. 실제로 배열을 두 배로 늘리지는 않지만 할당하여 자랍니다.

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

newsize요청 된 크기는 매번 용량 에 따라 달라집니다 (필수 요소 수를 하나씩 지정하는 대신 임의의 수의 요소 allocated + 1를 사용할 수 있기 때문은 아닙니다 ).extendappend

Python FAQ 도 참조하십시오 .


6
따라서 파이썬 목록을 반복 할 때 링크 된 목록만큼 느립니다. 모든 항목이 포인터 일뿐이므로 모든 요소가 캐시 누락을 일으킬 가능성이 높습니다.
— Kr0e

9
@ Kr0e : 후속 요소가 실제로 동일한 객체 인 경우는 아닙니다. :) 더 작거나 캐시가 더 친숙한 데이터 구조가 필요한 경우 array모듈 또는 NumPy가 선호됩니다.
— Fred Foo

@ Kr0e 나는 목록을 반복하는 것이 연결된 목록만큼 느리다고 말하지는 않지만 연결된 목록의 값 을 반복 하는 것은 연결된 목록만큼 느리다는 것을 Fred는 언급 한주의 사항과 함께 느립니다. 예를 들어 다른 목록으로 복사하기 위해 목록을 반복하는 것은 연결된 목록보다 빠릅니다.
— Ganea Dan Andrei

35

CPython에서 목록은 포인터의 배열입니다. 파이썬의 다른 구현은 다른 방법으로 저장하도록 선택할 수 있습니다.


32

이것은 구현에 따라 다르지만 IIRC :

  • CPython은 포인터 배열을 사용합니다
  • 자이 썬은 ArrayList
  • IronPython은 분명히 배열을 사용합니다. 소스 코드 를 찾아서 찾을 수 있습니다 .

따라서 그들은 모두 O (1) 랜덤 액세스를 갖습니다.


1
링크 된 목록으로 목록을 구현 한 파이썬 인터프리터와 같은 구현은 파이썬 언어의 유효한 구현입니까? 즉, O (1) 목록에 대한 임의 액세스가 보장되지 않습니까? 구현 세부 사항에 의존하지 않고 효율적인 코드를 작성하는 것이 불가능하지 않습니까?
— sepp2k

2
@sepp 나는 파이썬의리스트가 단지 순서가있는 콜렉션이라고 믿는다. 해당 구현의 구현 및 / 또는 성능 요구 사항은 명시 적으로 명시되어 있지 않습니다.
— NullUserException

6
@ sppe2k : 파이썬에는 실제로 표준 또는 공식 사양이 없기 때문에 ( "...은 다음과 같은 내용이 보장됩니다 ..."라는 문서가 있지만) "이것은 100 % 확신 할 수 없습니다" "종이로 보장됩니다." 그러나 O(1)목록 인덱싱은 매우 일반적이고 유효한 가정이기 때문에 구현을 감수하지 않습니다.

@Paul 그것은리스트의 기본 구현이 어떻게 수행되어야하는지에 대해서는 아무 것도 말하지 않습니다.
— NullUserException

큰 O 실행 시간을 지정하는 것은 아닙니다. 언어 구문 사양이 반드시 구현 세부 사항과 동일한 것을 의미하는 것은 아니며 종종 발생합니다.
— Paul McMillan

26

Laurent Luce의 기사 "Python list implementation"을 제안 합니다. 필자가 CPython에서 목록이 구현되는 방법을 설명 하고이 목적을 위해 훌륭한 다이어그램을 사용하기 때문에 정말 유용했습니다.

객체 C 구조 나열

CPython의 목록 오브젝트는 다음 C 구조로 표시됩니다. ob_item목록 요소에 대한 포인터 목록입니다. 할당은 메모리에 할당 된 슬롯 수입니다.

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

할당 된 슬롯과 목록 크기의 차이를 확인하는 것이 중요합니다. 목록의 크기는 len(l)입니다. 할당 된 슬롯 수는 메모리에 할당 된 수입니다. 종종, 할당 된 크기가 크기보다 클 수 있음을 알 수 있습니다. realloc새 요소가 목록에 추가 될 때마다 호출 할 필요 가 없습니다.

...

추가

리스트에 정수를 추가합니다 : l.append(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의 목록 크기입니다. 에list_resize() , 크기 - 1 = 4 - 1 = 3에서 6 개 슬롯 수축 및리스트의 새로운 크기가 현재 3 있도록 할당 된 슬롯의 절반 미만이다.

슬롯 3과 4가 여전히 일부 정수를 가리키는 것을 볼 수 있지만 중요한 것은 목록의 크기가 현재 3입니다.여기에 이미지 설명을 입력하십시오

...

Python 목록 제거 객체에는 특정 요소를 제거하는 방법이 l.remove(5)있습니다.여기에 이미지 설명을 입력하십시오


감사 합니다. 목록 의 링크 부분 이 더 이해 되었습니다. 파이썬리스트는 aggregation아닙니다 composition. 작곡 목록도 있었으면 좋겠다.
— shuva

22

에 따르면 문서 ,

파이썬의 목록은 실제로 가변 길이 배열이며 Lisp 스타일의 연결된 목록이 아닙니다.


5

다른 사람들이 위에서 언급했듯이, 목록 (눈에 띄게 큰 경우)은 고정 된 양의 공간을 할당하고, 그 공간이 채워지면 더 많은 양의 공간을 할당하고 요소를 복사하여 구현됩니다.

일반성을 잃지 않고 메소드가 O (1)로 상각되는 이유를 이해하려면 = 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) 상각 시간). 또한 테이블이 삭제를 허용하는 경우 테이블 축소는 다른 요소 (예 : 3x)로 수행되어야합니다.


내가 이해하는 한 오래된 요소는 복사하지 않습니다. 더 많은 공간이 할당되었지만 새 공간은 이미 사용중인 공간과 인접하지 않으며 삽입 할 최신 요소 만 새 공간에 복사됩니다. 내가 틀렸다면 정정 해주세요.
— Tushar Vazirani

1

파이썬의 목록은 배열과 유사하며 여러 값을 저장할 수 있습니다. 목록은 변경할 수 있으므로 변경할 수 있습니다. 더 중요한 것은리스트를 만들 때 파이썬은 자동으로 해당리스트 변수에 대한 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에서 list는 동적 배열로 구현되므로 그 시점에 추가 할 때마다 하나의 매크로가 추가 될뿐만 아니라 더 많은 공간이 할당되므로 매번 새로운 공간을 추가해서는 안됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.