프로그래밍백엔드 개발자

파이썬에서 리스트(list)란 무엇이며, 내부에서는 어떻게 구현되어 있으며, 주요 특징은 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

이 질문의 역사

리스트(list)는 파이썬의 기본 내장 데이터 유형 중 하나로, 언어의 초기 버전부터 존재했습니다. 시간이 지남에 따라 내부 구조가 개선되어 성능과 프로그래밍 편의성이 향상되었습니다.

문제

중요한 점은 파이썬의 리스트가 연결 리스트로 구현되지 않고 동적 배열로 구현된다는 것입니다. 잘못된 이해는 비효율적인 코드 또는 연산의 복잡도를 잘못 계산하는 결과를 초래할 수 있습니다.

해결책

리스트는 필요할 때 메모리 크기를 "점프"하여 늘리는 동적 배열로 구현되어 있어, 요소를 추가할 때 발생하는 지속적인 비용을 피할 수 있습니다. 이를 통해 리스트 끝에 요소를 삽입하거나 삭제하는 작업이 평균 O(1) 시간이 걸립니다. 중간이나 처음에 삽입하거나 삭제하는 작업은 요소 이동이 필요하기 때문에 O(n) 시간이 소요됩니다.

코드 예제:

my_list = [1, 2, 3] my_list.append(4) # 끝에 추가하기 my_list.insert(1, 'a') # 인덱스에 삽입하기 print(my_list)

주요 특징:

  • 사용자에 의해 명시적인 메모리 할당/해제가 없이 동적으로 크기를 변경할 수 있음.
  • 끝에서의 삽입 및 삭제는 빠른 반면, 시작/중간에서의 삽입과 삭제는 느림.
  • 리스트는 임의의 타입의 요소를 포함할 수 있음(헤테로지니어스).

속임수 질문.

파이썬의 리스트를 C의 배열과 같다고 볼 수 있을까요? 그 차이는 무엇인가요?

아니요, C의 배열은 고정된 타입의 요소들이 메모리에 연속적으로 저장됩니다. 파이썬 리스트는 객체에 대한 포인터 배열로, 각각의 포인터는 힙에 있는 자율적인 임의 타입 객체를 참조합니다.

루프에서 리스트의 시작 부분에 요소를 삽입하면 어떻게 되나요? 빠른가요?

아니요, 느립니다: 매번 (위치 0에) 요소를 삽입할 때마다 모든 기존 요소가 오른쪽으로 이동해야 합니다. 평균 복잡도는 O(n)이며, 큰 리스트의 경우 성능 저하를 초래합니다.

리스트를 위한 + 연산자와 append/extend의 차이는 무엇인가요?

  • 연산자는 새로운 리스트를 생성하며 기존 리스트는 변하지 않습니다. append는 요소를 원래 리스트의 끝에 추가합니다. extend는 다른 시퀀스의 모든 요소를 리스트에 추가하여 원래 객체를 변경합니다.
lst1 = [1, 2] lst2 = [3, 4] lst3 = lst1 + lst2 # lst3는 새로운 리스트 lst1.append(lst2) # lst1은 [1, 2, [3, 4]]가 됨 lst1.extend(lst2) # lst1은 [1, 2, 3, 4]가 됨, lst1이 이전 append 이후 리셋되지 않았을 경우

전형적인 오류 및 안티 패턴

  • 리스트를 시작 부분에서 자주 삽입/삭제하는 데 사용하기 (collections.deque를 사용하는 것이 좋음)
  • 리스트가 메모리를 적게 차지하고 C/Java의 배열처럼 "조밀한" 데이터를 포함한다고 잘못 기대하기
  • 리스트를 자기 자신 안에 우연히 중첩시키기 (리스트를 자기 자신의 리스트에 append하기)

실제 사례

부정적인 케이스

프로젝트에서 첫 요소를 저장하고 지속적으로 삭제하는 데 리스트가 사용되었습니다. 데이터 양이 많을 때 현저한 속도 저하가 발생했습니다.

장점:

  • 시작이 쉬우며 많은 내장 메서드가 있습니다.

단점:

  • 성능이 저하되었고 대량 데이터에서 메모리 문제 발생.

긍정적인 케이스

문제를 파악한 후, 리스트를 collections.deque로 교체하여 이러한 작업에 최적화되었습니다.

장점:

  • 성능이 크게 향상되어 코드가 훨씬 빨리 작동하게 됨.

단점:

  • 일부 인터페이스를 변경해야 했으며, deque에는 없는 메서드에 대한 제한이 생겼습니다.