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


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.