Python Programming
  • Home
  • Intro
    • History & Background
    • Python Setup
  • QPB
    • Part I: Chapter 1-3
    • Part II
    • 5. Lists, Tuples, Sets
  • Exercises
    • Chapter 5: Lists, Tuples, Sets
  • References
    • QPB Part 1
    • QPB Part 2
    • QPB Part 3
    • QPB Part 4

On this page

  • 강의 목표
  • 1. 파이썬 시퀀스 소개
  • 2. 리스트: 핵심 데이터 구조
  • 3. 튜플
  • 4. 집합

5장: 리스트, 튜플, 집합

강의 목표

  • 파이썬의 주요 시퀀스 타입인 리스트와 튜플의 개념과 사용법을 이해합니다.
  • 리스트의 생성, 인덱싱, 슬라이싱, 수정, 정렬 및 기타 공통 연산을 숙달합니다.
  • 튜플의 불변성 및 패킹/언패킹과 리스트와의 차이점을 파악합니다.
  • 집합(Set)의 개념, 생성 및 기본 연산을 학습합니다.
  • 중첩 리스트와 얕은 복사/깊은 복사의 중요성을 이해합니다.

1. 파이썬 시퀀스 소개

  • 파이썬의 주요 시퀀스 타입: 리스트(lists)와 튜플(tuples).
    • 리스트는 다른 언어의 배열과 유사하지만, 훨씬 더 유연하고 강력합니다.
    • 튜플은 수정할 수 없는 리스트와 같습니다. 제한된 형태의 리스트 또는 기본 레코드 타입으로 생각할 수 있습니다.
  • 새로운 컬렉션 타입: 집합(sets)
    • 객체의 컬렉션 내 멤버십(포함 여부)이 위치보다 중요할 때 유용합니다.

2. 리스트: 핵심 데이터 구조

2.1 리스트 생성 및 기본

  • 정의: 리스트는 객체의 정렬된(ordered) 컬렉션입니다.
  • 생성: 쉼표로 구분된 요소들을 대괄호 []로 묶어 생성합니다.
    • 예시: my_list = [1, 'hello', 3.14]
  • 동적 크기 조절: 미리 크기를 선언하거나 고정할 필요 없이, 필요에 따라 자동으로 커지거나 작아집니다.
  • 다양한 타입의 요소: 파이썬 리스트는 다른 언어의 리스트와 달리 다양한 타입의 요소를 포함할 수 있습니다 (모든 파이썬 객체 가능).
  • len() 함수: 리스트의 요소 개수를 반환합니다.
    • 주의: 중첩된 리스트 안의 항목은 세지 않습니다.

예제 코드:

# 리스트 생성
x = [1, 2, 3]  # 숫자 리스트
print(x)  # 출력: [1, 2, 3]

y = [2, "two", [1, 2, 3]]  # 혼합 타입
print(y)  # 출력: [2, 'two', [1, 2, 3]]

# 길이 확인
print(len(y))  # 출력: 3 (내부 리스트는 세지 않음)

2.2 리스트 인덱스 및 슬라이싱

  • 인덱싱: C 언어의 배열 인덱싱과 유사하게 0부터 시작합니다.
    • list은 첫 번째 요소, list은 두 번째 요소 등.
  • 음수 인덱싱: 리스트의 끝에서부터 위치를 나타냅니다.
    • -1은 마지막 요소, -2는 뒤에서 두 번째 요소 등.
  • 인덱스의 개념:
    • 단일 요소 추출 시에는 인덱스가 특정 요소를 가리킨다고 생각할 수 있습니다.
    • 고급 연산(슬라이싱) 시에는 인덱스가 요소 사이의 위치를 나타낸다고 생각하는 것이 더 정확합니다.
  • 슬라이싱: 리스트의 하위 리스트(sublist)를 한 번에 추출하거나 할당하는 작업.
    • list[index1:index2]: index1부터 index2 직전까지의 모든 항목을 추출하여 새 리스트로 만듭니다.
    • index2가 index1보다 앞선 경우 빈 리스트 []를 반환합니다.
    • 인덱스 생략:
      • index1 생략: 리스트의 처음부터.
      • index2 생략: 리스트의 끝까지.
      • 둘 다 생략: list[:]는 리스트 전체의 복사본을 만듭니다 (원본에 영향을 주지 않고 수정 가능).

예제 코드:

x = ["first", "second", "third", "fourth"]

# 인덱스 접근
print(x[0])  # 출력: 'first'
print(x[-1]) # 출력: 'fourth'

# 슬라이싱
print(x[1:-1]) # 출력: ['second', 'third']
print(x[:3])   # 출력: ['first', 'second', 'third']
print(x[2:])   # 출력: ['third', 'fourth']

# 두 번째 절반 (Try this)
n = len(x) // 2
print(x[n:])   # 출력: ['third', 'fourth']

# 전체 복사
y = x[:]  # 얕은 복사
y[0] = '1st'
print(y)  # 출력: ['1st', 'second', 'third', 'fourth']
print(x)  # 원본 유지: ['first', 'second', 'third', 'fourth']

2.3 리스트 수정

  • 인덱스를 이용한 수정: 할당 연산자 =의 왼쪽에 인덱스를 사용하여 요소를 수정할 수 있습니다.
    • 예시: my_list = 'new_value'
  • 슬라이스 할당: lista[index1:index2] = listb를 사용하여 lista의 해당 슬라이스를 listb의 요소들로 교체합니다.
    • listb의 요소 개수에 따라 lista의 길이가 변경될 수 있습니다.
  • 주요 리스트 메서드:
    • append(element): 리스트의 끝에 단일 요소를 추가합니다.
      • 주의: 다른 리스트를 append()하면 중첩 리스트로 추가됩니다.
    • extend(another_list): 한 리스트의 요소들을 다른 리스트의 끝에 확장하여 추가합니다.
    • insert(n, element): n번 인덱스 바로 앞에 element를 삽입합니다.
      • 음수 인덱스도 처리합니다.
    • del 문: 리스트의 항목 또는 슬라이스를 삭제하는 권장 방식.
      • del list[n], del list[m:n].
    • remove(value): 리스트에서 주어진 값의 첫 번째 인스턴스를 찾아 제거합니다.
      • 값을 찾지 못하면 오류를 발생시킵니다 (발생 방지를 위해 in 연산자 사용).
    • reverse(): 리스트를 제자리에서(in-place) 역순으로 만듭니다.

예제 코드:

x = [1, 2, 3, 4]

# 인덱스 수정
x[1] = "two"
print(x)  # 출력: [1, 'two', 3, 4]

# 슬라이스 할당
x[1:3] = ["new", "data"]
print(x)  # 출력: [1, 'new', 'data', 4]

# 메서드
x.append(5)
print(x)  # 출력: [1, 'new', 'data', 4, 5]
x.extend([6, 7])
print(x)  # 출력: [1, 'new', 'data', 4, 5, 6, 7]
x.insert(0, 0)
print(x)  # 출력: [0, 1, 'new', 'data', 4, 5, 6, 7]
del x[1:3]
print(x)  # 출력: [0, 'data', 4, 5, 6, 7]
x.remove(4)
print(x)  # 출력: [0, 'data', 5, 6, 7]
x.reverse()
print(x)  # 출력: [7, 6, 5, 'data', 0]

# Try this: 마지막 3개 요소를 앞으로
x = list(range(10))  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
x = x[-3:] + x[:-3]
print(x)  # 출력: [7, 8, 9, 0, 1, 2, 3, 4, 5, 6]

2.4 리스트 정렬

  • list.sort() 메서드: 리스트를 제자리에서(in-place) 정렬합니다.
    • 원본 리스트를 변경합니다.
    • 문자열도 정렬 가능합니다.
  • 정렬 시 주의사항: 기본 정렬 메서드는 리스트의 모든 항목이 비교 가능한 타입이어야 합니다. 그렇지 않으면 예외가 발생합니다.
  • sorted() 내장 함수: 어떤 이터러블(iterable)이든 새로운 정렬된 리스트를 반환합니다.
    • 원본을 변경하지 않고 정렬된 복사본을 만들 때 사용합니다.
    • key 및 reverse 매개변수를 sort() 메서드와 동일하게 사용합니다.
  • 정렬 복사본 만들기: list[:] .sort() 또는 sorted(list).
  • 커스텀 정렬:
    • key 매개변수 사용: 정렬 기준으로 사용할 값을 반환하는 함수를 전달합니다.
    • 예시: 문자열 길이에 따라 정렬하기 위해 len() 함수를 key로 사용.
    • 람다(lambda) 함수: 짧고 한 번만 사용되는 키 함수를 만들 때 유용합니다.
    • reverse=True: 내림차순 정렬 시 커스텀 정렬 대신 이 매개변수를 사용하는 것이 더 빠릅니다.
    • 성능 고려: 커스텀 정렬은 기본 정렬보다 느릴 수 있습니다.

예제 코드:

x = [3, 8, 4, 0, 2, 1]
x.sort()
print(x)  # 출력: [0, 1, 2, 3, 4, 8]

# 커스텀 정렬
words = ['Python', 'is', 'better', 'than', 'C']
words.sort(key=len)
print(words)  # 출력: ['C', 'is', 'than', 'Python', 'better']

# sorted()와 reverse
y = sorted(words, reverse=True)
print(y)  # 출력: ['than', 'Python', 'is', 'better', 'C']

# Try this: 두 번째 요소 기준 정렬
x = [[1, 2, 3], [2, 1, 3], [4, 0, 1]]
x.sort(key=lambda x: x[1])
print(x)  # 출력: [[4, 0, 1], [2, 1, 3], [1, 2, 3]]

2.5 기타 공통 리스트 연산

  • in 연산자: 값이 리스트에 있는지 확인합니다 (Boolean 값 반환). not in도 사용 가능.
  • + 연산자 (리스트 연결): 두 리스트를 연결하여 새로운 리스트를 생성합니다 (원본 리스트는 변경되지 않음).
  • * 연산자 (리스트 곱셈): 리스트를 지정된 횟수만큼 복제하여 새로운 리스트를 만듭니다.
    • 미리 크기를 알고 있는 큰 리스트를 초기화할 때 효율적입니다.
  • min(), max(): 리스트에서 가장 작은/큰 요소를 찾습니다.
    • 주의: 서로 비교할 수 없는 타입의 요소가 섞여 있으면 오류 발생.
  • index(value) 메서드: 리스트에서 주어진 값의 위치를 반환합니다.
    • 주의: 값이 리스트에 없으면 오류 발생 (발생 방지를 위해 in 연산자 사용).
  • count(value) 메서드: 리스트에서 주어진 값이 나타나는 횟수를 반환합니다.
  • Quick check: len([] * 3)의 결과, in 연산자와 index() 메서드의 두 가지 차이점, 예외를 발생시키는 경우.
  • Try this: 리스트 x에서 특정 값이 있을 경우에만 안전하게 항목을 제거하는 코드, 해당 항목이 두 번 이상 나타날 경우에만 제거하는 코드로 수정.

예제 코드:

x = [1, 2, 3, 2, 5]
print(3 in x)     # 출력: True
print(x + [6, 7]) # 출력: [1, 2, 3, 2, 5, 6, 7]
print([None] * 3) # 출력: [None, None, None]
print(min(x))     # 출력: 1
print(x.index(2)) # 출력: 1
print(x.count(2)) # 출력: 2

2.6 중첩 리스트와 깊은 복사

  • 중첩 리스트: 리스트 안에 다른 리스트를 포함할 수 있습니다. 2차원 행렬 등을 표현하는 데 사용됩니다.
    • matrix[row][col]와 같이 2차원 인덱스를 사용합니다.
  • 변수의 객체 참조: 파이썬 변수는 객체를 참조합니다. 리스트는 변경 가능한(mutable) 객체입니다.
    • 얕은 복사(Shallow copy): x[:], x + [], x * 1 등으로 생성합니다.
      • 최상위 리스트는 새로운 객체지만, 중첩된 리스트는 원본과 동일한 객체를 참조합니다.
      • 따라서 얕은 복사본의 중첩된 리스트를 변경하면 원본의 중첩된 리스트도 변경됩니다.
  • 깊은 복사(Deep copy): copy 모듈의 deepcopy() 함수를 사용합니다.
    • 모든 중첩된 객체까지 완전히 독립적인 새 객체로 복사합니다.
    • 깊은 복사본을 변경해도 원본 리스트에는 영향을 주지 않습니다.
  • Try this: x = [,,] 리스트가 있을 때, x의 내용을 변경하지 않고 y를 수정할 수 있는 복사본을 얻는 코드.

예제 코드:

import copy
x = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
y = x[:]  # 얕은 복사
y[0][0] = 0
print(x)  # 출력: [[0, 2, 3], [4, 5, 6], [7, 8, 9]] (원본 변경)
z = copy.deepcopy(x)
z[0][0] = 5
print(x)  # 출력: [[0, 2, 3], [4, 5, 6], [7, 8, 9]] (원본 유지)

3. 튜플

3.1 튜플 기본

  • 정의: 리스트와 매우 유사하지만, 수정할 수 없는(immutable) 데이터 구조입니다.
  • 생성: 쉼표로 구분된 값들을 괄호 ()로 묶어 생성합니다.
    • 예시: my_tuple = ('a', 'b', 'c')
  • 불변성: 튜플은 생성 후 수정할 수 없습니다. 수정 시 TypeError가 발생합니다.
  • + 및 * 연산자를 사용하여 기존 튜플로부터 새로운 튜플을 만들 수 있습니다.
  • “복사본”: 튜플은 불변이기 때문에, 리스트처럼 [:] 등으로 “복사”해도 실제로는 원본 객체를 가리키는 별칭(alias)을 반환합니다.
    • 주의: 튜플 안에 리스트나 딕셔너리 같은 변경 가능한 객체가 포함되어 있다면, 해당 객체들은 변경될 수 있습니다. (이런 튜플은 딕셔너리 키로 사용할 수 없습니다).

3.2 한 요소 튜플의 특이점

  • 쉼표의 중요성: 괄호 ()는 표현식에서 항목을 묶는 데도 사용될 수 있기 때문에, 한 요소 튜플을 만들 때는 요소 뒤에 쉼표를 반드시 붙여야 합니다.
    • 예시: my_one_element_tuple = (1,)
    • x = (1)은 튜플이 아니라 int 타입의 x = 1이 됩니다.
  • 빈 튜플: ()로 생성하며, 모호성이 없어 쉼표가 필요 없습니다.

3.3 튜플 패킹 및 언패킹

  • 패킹(Packing): 여러 값을 하나의 튜플로 묶는 것.
  • 언패킹(Unpacking): 튜플의 값을 여러 변수에 할당하는 것.
    • one, two, three = 1, 2, 3 와 같이 괄호를 생략하고 사용 가능.
    • 변수 간 값 교환 시 매우 편리합니다.
  • 확장 언패킹 (Python 3): *를 사용하여 남은 모든 요소를 리스트로 흡수할 수 있습니다.
    • 예시: first, *middle, last = 1, 2, 3, 4, 5
  • 리스트도 패킹/언패킹 가능합니다.

예제 코드:

a, b, c = 1, 2, 3
print(a, b, c)  # 출력: 1 2 3
a, b = b, a
print(a, b)     # 출력: 2 1
x = (1, 2, 3, 4)
a, *b, c = x
print(a, b, c)  # 출력: 1 [2, 3] 4

3.4 리스트와 튜플 간 변환

  • list(sequence): 어떤 시퀀스(튜플 포함)든 인자로 받아 새로운 리스트를 생성합니다.
    • list("Hello")는 ['H', 'e', 'l', 'l', 'o']를 반환합니다.
  • tuple(sequence): 어떤 시퀀스(리스트 포함)든 인자로 받아 새로운 튜플을 생성합니다.

예제 코드:

list((1, 2))  # 출력: [1, 2]
tuple([3, 4]) # 출력: (3, 4)

list("Hello")  # 출력: ['H', 'e', 'l', 'l', 'o']

x = (3, 1, 4, 2)
sorted(x)  # 출력: [1, 2, 3, 4]

4. 집합

4.1 집합의 특성 및 생성

  • 정의: 객체의 정렬되지 않은(unordered) 컬렉션이며, 유일한(unique) 요소만을 가집니다.
  • 요소의 제약: 딕셔너리 키와 마찬가지로, 집합의 항목은 불변(immutable)이며 해시 가능(hashable)해야 합니다.
    • int, float, string, tuple은 가능.
    • 리스트, 딕셔너리, 집합 자체는 불가능.
  • 생성: set(sequence) 함수를 사용하여 시퀀스(예: 리스트)로부터 집합을 생성합니다.
    • 중복된 요소는 제거됩니다.
    • 예시: my_set = set()

예제 코드:

x = set([1, 2, 3, 1, 3, 5])
x  # {1, 2, 3, 5}

4.2 집합 연산

  • 멤버십 확인: in 키워드.
  • 요소 추가/제거: add(element), remove(element).
  • 수학적 집합 연산:
    • | (Union/합집합): 두 집합의 모든 요소를 포함하는 새 집합.
    • & (Intersection/교집합): 두 집합에 공통으로 있는 요소를 포함하는 새 집합.
    • ^ (Symmetric Difference/대칭 차집합): 한쪽 집합에는 있지만 양쪽에 모두 있지는 않은 요소를 포함하는 새 집합.

예제 코드:

x = set([1, 2, 3, 5])
x.add(6)
print(x)  # 출력: {1, 2, 3, 5, 6}
y = set([4, 5, 6])
print(x | y)  # 출력: {1, 2, 3, 4, 5, 6}
print(x & y)  # 출력: {5, 6}
print(x ^ y)  # 출력: {1, 2, 3, 4}

4.3 불변 집합: frozenset

  • frozenset: 일반 set과 달리 생성 후 변경할 수 없는(immutable) 집합 타입.
  • 활용: 불변이며 해시 가능하기 때문에 다른 집합의 멤버로 포함될 수 있습니다.
    • 예시: my_frozenset = frozenset(my_set)

예제 코드:

z = frozenset([1, 2, 3])
x.add(z)
print(x)  # 출력: {1, 2, 3, 5, 6, frozenset({1, 2, 3})}
Part I: Chapter 1-3

This work © 2025 by Sungkyun Cho is licensed under CC BY-NC-SA 4.0