현재 선택한 정보처리 과정

정보처리기사 필기 이론 학습

이론 목록으로 돌아가기

Python 시퀀스·세트·딕셔너리·반복

Python의 리스트·튜플·range·세트·딕셔너리를 순서·중복·변경 가능성으로 구분한다. 인덱싱과 슬라이싱, 별칭과 복사, 반복문과 컴프리헨션에서 원소와 상태가 바뀌는 과정을 추적한다. 수치 합산과 원본 객체의 상태 변화도 구분한다.

예상 읽기 27

Python의 리스트·튜플·range·세트·딕셔너리를 순서·중복·변경 가능성으로 구분한다. 인덱싱과 슬라이싱, 별칭과 복사, 반복문과 컴프리헨션에서 원소와 상태가 바뀌는 과정을 추적한다.

그림으로 확인하기

좌우로 이동해 그림을 확인하세요.그림 크게 보기
새 바깥 리스트가 만들어져도 내부 가변 객체는 공유될 수 있다.
새 바깥 리스트가 만들어져도 내부 가변 객체는 공유될 수 있다.

컨테이너를 고르는 네 가지 기준

자료구조 이름을 먼저 외우기보다 다음 네 질문을 순서대로 적용한다.

  1. 순서와 위치가 필요한가? 정수 위치로 원소를 찾고 슬라이스해야 하면 시퀀스를 검토한다.
  2. 중복을 허용하는가? 같은 원소를 한 번만 보관하려면 세트를 검토한다.
  3. 키로 값을 찾아야 하는가? 사번·상품 코드·단어처럼 식별자를 통해 값을 찾으려면 딕셔너리를 검토한다.
  4. 컨테이너 자체를 바꿔야 하는가? 원소 추가·삭제·교체가 필요하면 가변 컨테이너를 선택한다.
순서·위치중복변경 가능성주된 조회 기준
list있음허용가변정수 인덱스·순회
tuple있음허용불변정수 인덱스·순회
range규칙에 따른 순서값이 겹치지 않는 산술 수열불변정수 인덱스·순회
set위치 계약 없음허용하지 않음가변원소 포함 여부
frozenset위치 계약 없음허용하지 않음불변원소 포함 여부
dict삽입 순서 보존키는 고유, 값은 중복 가능가변해시 가능한 키

dictset도 반복할 수 있으므로 이터러블이지만 시퀀스는 아니다. 반복 가능하다는 사실과 정수 인덱스로 위치 접근이 가능하다는 사실을 같은 뜻으로 보면 안 된다.

시퀀스의 공통 연산

Python의 기본 시퀀스에는 리스트, 튜플, range가 있다. 문자열도 시퀀스이지만 이 이론에서는 컨테이너 선택과 상태 추적에 필요한 규칙만 다룬다.

연산의미주의점
x in sx와 같은 원소가 있는지 검사위치를 반환하지 않고 True 또는 False를 반환한다.
s + t같은 계열 시퀀스를 연결새 시퀀스를 만들며 range는 연결을 지원하지 않는다.
s * n원소 참조를 n회 반복한 새 시퀀스 생성중첩 가변 객체를 실제로 n개 복제하지 않는다.
s[i]0부터 시작하는 위치 i의 원소범위를 벗어나면 IndexError가 발생한다.
s[i:j:k]시작·끝·간격으로 부분 시퀀스 선택j는 포함하지 않으며 k는 0일 수 없다.
len(s)원소 수range도 길이를 계산할 수 있다.
s.count(x)x와 같은 원소의 개수대부분의 기본 시퀀스가 제공한다.
s.index(x)첫 번째 x의 위치찾지 못하면 ValueError가 발생한다.

다음 코드는 같은 값을 찾는 연산과 위치를 찾는 연산을 구분한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [10, 20, 10, 30]

print(10 in values)       # True
print(values.count(10))   # 2
print(values.index(10))   # 0

index()는 모든 위치를 반환하지 않고 첫 번째 위치만 반환한다. values.index(99)처럼 없는 값을 찾으면 -1이 아니라 ValueError가 발생한다.

리스트: 가변 시퀀스의 상태 변화

원소 추가·삭제·교체

리스트는 원소를 직접 바꾸는 가변 시퀀스다.

연산·메서드상태 변화반환값·예외
a.append(x)끝에 x 하나를 원소로 추가None
a.extend(iterable)이터러블의 각 원소를 끝에 이어 붙임None
a.insert(i, x)위치 i 앞에 x 삽입None
a.remove(x)값이 같은 첫 원소 삭제반환값 None, 없으면 ValueError
a.pop()마지막 원소 삭제삭제한 원소 반환, 비어 있으면 IndexError
a.pop(i)위치 i의 원소 삭제삭제한 원소 반환
a.clear()모든 원소 삭제None
del a[i]위치 i의 원소 삭제값을 반환하지 않는 문장
a[i] = x위치 i의 원소 교체대입문 자체의 반환값 없음

appendextend는 결과 모양이 다르다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
first = [1, 2]
first.append([3, 4])
print(first)               # [1, 2, [3, 4]]

second = [1, 2]
second.extend([3, 4])
print(second)              # [1, 2, 3, 4]

append([3, 4])는 리스트 한 개를 원소로 추가한다. extend([3, 4])는 전달된 이터러블을 순회해 34를 각각 추가한다. 문자열을 extend하면 문자 단위로 추가된다는 점도 같은 원리다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
letters = ["A"]
letters.extend("BC")
print(letters)             # ['A', 'B', 'C']

값을 삭제하는 remove와 위치를 삭제하는 pop

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [10, 20, 10, 30]
values.remove(10)
removed = values.pop(1)

print(values)              # [20, 30]
print(removed)             # 10
  1. remove(10)은 첫 번째 10만 지워 [20, 10, 30]이 된다.
  2. 이어서 pop(1)은 현재 위치 1의 10을 지우고 그 값을 반환한다.
  3. 최종 리스트는 [20, 30]이다.

remove의 인자는 값이고 pop의 인자는 위치다. 이 차이를 거꾸로 쓰는 선지가 자주 나온다.

제자리 변경 메서드와 새 결과를 만드는 함수

리스트의 sort()reverse()는 원본을 제자리에서 바꾸고 None을 반환한다. 반면 sorted(iterable)은 원본을 그대로 두고 새 리스트를 반환한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [3, 1, 2]
result = values.sort()

print(values)              # [1, 2, 3]
print(result)              # None

따라서 다음 코드는 정렬된 리스트를 result에 저장하지 못한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [3, 1, 2]
result = values.sort()     # result는 None

새 결과가 필요하면 다음처럼 쓴다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [3, 1, 2]
result = sorted(values)

print(values)              # [3, 1, 2]
print(result)              # [1, 2, 3]

sort()sorted()는 모두 같은 정렬 키를 가진 원소의 상대적 순서를 유지하는 안정 정렬을 제공한다. 그러나 반환값과 원본 변경 여부는 다르다.

++=의 객체 동일성

리스트에서 +는 새 리스트를 만들고, +=는 일반적으로 기존 리스트를 제자리에서 확장한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
left = [1, 2]
alias = left
left += [3]

print(left)                # [1, 2, 3]
print(alias)               # [1, 2, 3]
print(left is alias)       # True

alias도 같은 리스트를 가리키므로 변경을 함께 본다. 반면 튜플은 불변이라 +=가 새 튜플을 만들어 이름을 다시 연결한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
left = (1, 2)
alias = left
left += (3,)

print(left)                # (1, 2, 3)
print(alias)               # (1, 2)
print(left is alias)       # False

튜플과 range

튜플은 괄호보다 쉼표가 핵심이다

튜플은 불변 시퀀스다. 한 원소 튜플은 괄호만으로 만들 수 없고 쉼표가 필요하다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
print(type((10)))          # <class 'int'>
print(type((10,)))         # <class 'tuple'>

single = 10,
print(type(single))        # <class 'tuple'>

(10)은 우선순위를 위한 괄호일 뿐이다. (10,) 또는 10,처럼 쉼표가 있어야 한 원소 튜플이 된다.

튜플의 불변성은 튜플이 각 위치에서 어떤 객체를 가리키는지 바꿀 수 없다는 뜻이다. 원소가 리스트 같은 가변 객체이면 그 객체 내부는 바뀔 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
record = ("A", [1, 2])
record[1].append(3)
print(record)              # ('A', [1, 2, 3])

# record[0] = "B"         # TypeError: 튜플 위치 교체 불가

이 튜플은 내부에 해시 불가능한 리스트를 포함하므로 딕셔너리 키나 세트 원소로 사용할 수 없다. 불변 컨테이너이면 언제나 해시 가능하다는 설명은 틀리다.

패킹과 언패킹

쉼표로 여러 값을 묶으면 튜플 패킹이 일어나고, 여러 대상에 나누어 대입하면 언패킹이 일어난다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
point = 3, 4
x, y = point

print(point)               # (3, 4)
print(x, y)                # 3 4

별표 대상은 남은 원소를 리스트로 받는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
first, *middle, last = [10, 20, 30, 40]
print(first)               # 10
print(middle)              # [20, 30]
print(last)                # 40

대상 수가 맞지 않고 별표 대상도 없으면 ValueError가 발생한다.

range는 숫자 목록이 아니라 불변 수열 표현이다

range(stop) 또는 range(start, stop, step)은 산술 규칙을 표현하는 불변 시퀀스다. 기본값은 start=0, step=1이고 stop은 포함하지 않는다. step=0이면 ValueError가 발생한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
print(list(range(5)))              # [0, 1, 2, 3, 4]
print(list(range(2, 8, 2)))        # [2, 4, 6]
print(list(range(5, 0, -2)))       # [5, 3, 1]
print(list(range(2, 8, -1)))       # []

양의 간격인데 시작값이 끝값 이상이거나, 음의 간격인데 시작값이 끝값 이하이면 빈 수열이 된다. range는 모든 값을 리스트에 미리 저장하는 방식이 아니라 시작·끝·간격 규칙으로 값을 계산하므로 큰 범위를 표현할 때 전체 목록을 만들 필요가 없다.

range도 시퀀스이므로 인덱싱, 음수 인덱싱, 슬라이싱, 포함 검사를 지원한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
numbers = range(10, 20, 2)
print(numbers[0])           # 10
print(numbers[-1])          # 18
print(list(numbers[1:4]))   # [12, 14, 16]
print(16 in numbers)        # True

range 객체는 생성 인자가 달라도 실제로 표현하는 수열이 같으면 동등할 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
print(range(0, 3, 2) == range(0, 4, 2))  # True

두 객체 모두 [0, 2]를 표현하기 때문이다.

인덱싱과 슬라이싱

단일 인덱스

인덱스는 0부터 시작한다. 음수 인덱스는 끝에서부터 센다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = ["A", "B", "C", "D", "E", "F"]

print(values[0])            # A
print(values[-1])           # F
print(values[-3])           # D

길이가 6인 시퀀스에서 -1은 위치 5, -2는 위치 4로 해석한다. 단일 인덱스가 범위를 벗어나면 IndexError가 발생한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [10, 20, 30]
# print(values[3])          # IndexError
# print(values[-4])         # IndexError

슬라이스의 세 요소

슬라이스는 s[start:stop:step] 형태다.

  • start 위치는 포함한다.
  • stop 위치는 포함하지 않는다.
  • step은 이동 간격이며 0일 수 없다.
  • step이 양수이면 왼쪽에서 오른쪽으로 진행한다.
  • step이 음수이면 오른쪽에서 왼쪽으로 진행한다.
PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = ["A", "B", "C", "D", "E", "F"]

print(values[1:5])          # ['B', 'C', 'D', 'E']
print(values[1:5:2])        # ['B', 'D']
print(values[-4:-1])        # ['C', 'D', 'E']
print(values[4:1:-1])       # ['E', 'D', 'C']
print(values[::-2])         # ['F', 'D', 'B']

values[4:1:-1]은 위치 4에서 시작해 1보다 큰 위치까지만 이동하므로 위치 4, 3, 2를 선택한다. 음의 간격에서도 stop은 포함하지 않는다.

startstop을 생략했을 때의 기본 방향은 step의 부호에 따라 달라진다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [0, 1, 2, 3, 4]

print(values[:3])           # [0, 1, 2]
print(values[2:])           # [2, 3, 4]
print(values[::-1])         # [4, 3, 2, 1, 0]

values[::-1]은 음의 간격으로 전체를 순회해 역순의 새 시퀀스를 만든다. 이를 values[0:len(values):-1]과 같은 뜻으로 보면 안 된다.

슬라이스 경계는 조정되지만 간격 0은 오류다

단일 인덱스와 달리 슬라이스의 경계는 실제 길이를 벗어나도 가능한 범위로 조정된다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [10, 20, 30]

print(values[100:200])      # []
print(values[-100:100])     # [10, 20, 30]

그러나 간격 0은 진행 방향을 정할 수 없으므로 오류다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [10, 20, 30]
# values[::0]               # ValueError

리스트 슬라이스 대입

리스트는 슬라이스 범위를 다른 이터러블의 원소로 교체할 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [0, 1, 2, 3, 4]
values[1:4] = [10, 20]
print(values)               # [0, 10, 20, 4]

간격이 1인 슬라이스 대입은 교체하는 원소 수가 달라도 리스트 길이가 조정된다. 그러나 간격이 1이 아닌 확장 슬라이스 대입은 선택된 위치 수와 새 원소 수가 같아야 한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = [0, 1, 2, 3, 4, 5]
values[::2] = [10, 20, 30]
print(values)               # [10, 1, 20, 3, 30, 5]

# values[::2] = [7, 8]      # ValueError: 선택 위치는 3개인데 값은 2개

이름 연결, 객체 동일성, 복사

대입은 복사가 아니다

Python에서 이름은 객체에 연결된다. 다음 두 이름은 같은 리스트를 가리킨다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
original = [1, 2]
alias = original
alias.append(3)

print(original)             # [1, 2, 3]
print(alias is original)    # True

==는 일반적으로 값의 동등성을 검사하고, is는 두 이름이 같은 객체를 가리키는지 검사한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
left = [1, 2]
right = [1, 2]

print(left == right)        # True
print(left is right)        # False

숫자나 문자열의 값 비교에 is를 쓰면 구현의 객체 재사용에 의존할 수 있다. 값 비교에는 ==, None 같은 단일 객체 확인에는 is를 사용한다.

얕은 복사

리스트 전체 슬라이스, list.copy(), list(existing_list)는 바깥 리스트를 새로 만들지만 원소 객체까지 복제하지 않는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
original = [[1], [2]]
shallow = original.copy()

print(shallow is original)          # False
print(shallow[0] is original[0])    # True

shallow[0].append(9)
print(original)                     # [[1, 9], [2]]
print(shallow)                      # [[1, 9], [2]]

바깥 리스트는 서로 다르지만 첫 번째 내부 리스트는 공유한다. 따라서 내부 리스트의 변경이 양쪽에서 보인다.

깊은 복사

중첩 가변 객체까지 독립적으로 복사해야 할 때 copy.deepcopy()를 사용할 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
import copy

original = [[1], [2]]
deep = copy.deepcopy(original)
deep[0].append(9)

print(original)             # [[1], [2]]
print(deep)                 # [[1, 9], [2]]

깊은 복사가 항상 정답인 것은 아니다. 의도적으로 공유해야 하는 객체까지 복제할 수 있으므로 어떤 수준을 독립시킬지 먼저 정해야 한다.

시퀀스 반복의 공유 함정

* 연산은 원소 객체를 깊게 복제하지 않고 같은 참조를 여러 위치에 반복한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
rows = [[0, 0]] * 3
rows[0][0] = 9
print(rows)                 # [[9, 0], [9, 0], [9, 0]]

세 행이 서로 다른 내부 리스트가 아니라 같은 내부 리스트를 세 번 가리키기 때문이다. 독립된 행이 필요하면 반복할 때마다 새 리스트를 만든다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
rows = [[0, 0] for _ in range(3)]
rows[0][0] = 9
print(rows)                 # [[9, 0], [0, 0], [0, 0]]

세트의 기본 연산

생성과 기본 성질

세트는 해시 가능한 원소의 중복 없는 집합이다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = {10, 20, 10, 30}
print(len(values))          # 3
print(20 in values)         # True

빈 중괄호 {}는 빈 세트가 아니라 빈 딕셔너리다. 빈 세트는 set()으로 만든다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
print(type({}))             # <class 'dict'>
print(type(set()))          # <class 'set'>

세트에는 위치가 없으므로 다음 연산을 지원하지 않는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = {10, 20, 30}
# values[0]                 # TypeError
# values[0:2]               # TypeError

반복은 가능하지만 출력·반복 순서를 정답 계산에 사용하면 안 된다. 결정적인 출력이 필요하면 비교 가능한 원소에 한해 sorted(values)처럼 명시적으로 정렬한다.

집합 연산

연산기호결과
합집합a | b어느 한쪽에라도 있는 원소
교집합a & b양쪽 모두에 있는 원소
차집합a - ba에는 있고 b에는 없는 원소
대칭 차집합a ^ b한쪽에만 있는 원소
부분 집합a <= ba의 모든 원소가 b에 있는지 검사
진부분 집합a < b부분 집합이면서 서로 다른지 검사
서로소a.isdisjoint(b)공통 원소가 없는지 검사
PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
left = {1, 2, 3}
right = {3, 4}

print(sorted(left | right))         # [1, 2, 3, 4]
print(sorted(left & right))         # [3]
print(sorted(left - right))         # [1, 2]
print(sorted(left ^ right))         # [1, 2, 4]

연산자형 &, |, -, ^는 다른 피연산자도 세트여야 한다. 메서드형 intersection, union, difference 등은 일반 이터러블도 인자로 받을 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
letters = set("abc")
print(letters.intersection("cde"))  # {'c'}와 동등한 세트
# letters & "cde"                   # TypeError

세트 자체의 출력 순서는 고정하지 않으므로 위 결과를 문자열 모양 그대로 암기하지 않는다.

원소 추가·삭제

메서드동작원소가 없을 때
s.add(x)원소 x 추가이미 있으면 변화 없음
s.update(iterable)여러 원소 추가각 원소를 순회해 추가
s.remove(x)원소 x 삭제KeyError
s.discard(x)원소 x 삭제아무 일도 하지 않음
s.pop()임의의 원소 하나 삭제 후 반환빈 세트이면 KeyError
s.clear()모든 원소 삭제빈 세트가 됨

set.pop()을 리스트의 pop()처럼 마지막 원소 삭제로 해석하면 안 된다. 세트에는 마지막 위치라는 계약이 없다.

딕셔너리: 고유 키와 값의 매핑

키와 값

딕셔너리는 해시 가능한 키를 임의의 값에 연결한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
student = {"id": 17, "name": "Kim", "scores": [80, 90]}
  • 키는 고유해야 한다.
  • 키는 해시 가능해야 한다.
  • 값은 해시 가능할 필요가 없으며 중복되어도 된다.
  • 같은 키에 다시 대입하면 새 항목을 추가하지 않고 기존 값을 갱신한다.
PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
scores = {"A": 10, "B": 20}
scores["A"] = 99
print(scores)               # {'A': 99, 'B': 20}
print(len(scores))          # 2

키 조회와 기본값

표현키가 있을 때키가 없을 때딕셔너리 변경
d[key]값 반환KeyError없음
d.get(key)값 반환None없음
d.get(key, default)값 반환default없음
d.setdefault(key, default)기존 값 반환default를 삽입하고 반환없을 때 삽입
key in d키 포함 여부 반환False없음

get()은 기본값을 반환해도 키를 삽입하지 않는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
counts = {}
print(counts.get("red", 0))  # 0
print(counts)                 # {}

반면 setdefault()는 키가 없으면 실제 항목을 만든다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
groups = {}
items = groups.setdefault("A", [])
items.append(10)
print(groups)               # {'A': [10]}

in은 키를 검사한다

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
scores = {"Kim": 90, "Lee": 80}

print("Kim" in scores)             # True
print(90 in scores)                # False
print(90 in scores.values())       # True
print(("Kim", 90) in scores.items())  # True

딕셔너리를 직접 반복해도 기본적으로 키가 나온다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
scores = {"Kim": 90, "Lee": 80}
for key in scores:
    print(key)

키와 값을 함께 사용하려면 items()를 사용한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
scores = {"Kim": 90, "Lee": 80}
for name, score in scores.items():
    print(name, score)

삽입 순서는 보존하지만 위치 인덱스는 아니다

딕셔너리는 키가 처음 삽입된 순서를 보존한다. 기존 키의 값을 갱신해도 그 키의 위치는 바뀌지 않는다. 키를 삭제한 뒤 다시 삽입하면 새 항목으로서 끝에 놓인다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = {"a": 1, "b": 2, "c": 3}
values["a"] = 10
print(list(values))         # ['a', 'b', 'c']

del values["b"]
values["b"] = 20
print(list(values))         # ['a', 'c', 'b']

그러나 삽입 순서 보존은 시퀀스 인덱싱을 의미하지 않는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
values = {"a": 1, "b": 2}
# values[0]                 # 키 0을 찾으므로 KeyError

딕셔너리의 동등성은 삽입 순서가 아니라 같은 키가 같은 값에 대응하는지로 판단한다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
left = {"a": 1, "b": 2}
right = {"b": 2, "a": 1}
print(left == right)        # True

딕셔너리 키와 세트 원소

딕셔너리의 키와 세트 원소에는 해시 가능한 객체를 사용한다. 정수·문자열은 대표적으로 가능하지만, 변경 가능한 리스트나 딕셔너리는 그대로 키나 세트 원소로 사용할 수 없다. 튜플은 내부 원소도 해시 가능할 때 사용할 수 있다.

반복과 이터레이터의 기본

리스트·튜플·문자열·range·딕셔너리 등은 for 문으로 원소를 순회할 수 있다. 이터레이터는 순회 위치를 유지하며 next()로 다음 값을 얻는 객체이다. 더 이상 값이 없으면 StopIteration으로 종료를 알리고, for 문은 이를 반복 종료로 처리한다. 한 번 소진된 같은 이터레이터는 자동으로 처음으로 돌아가지 않는다.

enumerate(values)는 원소의 순번과 값을 함께 제공한다. zip(a, b)는 같은 차례의 원소를 묶고 기본적으로 더 짧은 입력이 끝나면 멈춘다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
names = ["Kim", "Lee", "Park"]
scores = [90, 80]
print(list(enumerate(names, start=1)))
print(list(zip(names, scores)))

출력:

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
[(1, 'Kim'), (2, 'Lee'), (3, 'Park')]
[('Kim', 90), ('Lee', 80)]

딕셔너리를 직접 반복하면 키를 얻는다. 키와 값을 함께 사용하려면 items()를 사용한다. 반복 중 딕셔너리·세트의 크기를 바꾸거나 리스트에서 원소를 지우면 오류나 예상하지 못한 원소 누락이 생길 수 있으므로 새 결과를 구성하는 편이 명확하다.

컴프리헨션

리스트·세트·딕셔너리 컴프리헨션

컴프리헨션은 이터러블을 순회하면서 원소를 변환하거나 조건으로 거른 결과를 새 컨테이너에 담는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
numbers = [1, 2, 3, 4]

squares = [x * x for x in numbers]
even_squares = [x * x for x in numbers if x % 2 == 0]
unique_remainders = {x % 3 for x in numbers}
square_map = {x: x * x for x in numbers}

print(squares)              # [1, 4, 9, 16]
print(even_squares)         # [4, 16]
print(sorted(unique_remainders))  # [0, 1, 2]
print(square_map)           # {1: 1, 2: 4, 3: 9, 4: 16}
  • [...] 컴프리헨션은 리스트를 만든다.
  • {expression for ...}는 세트를 만든다.
  • {key: value for ...}는 딕셔너리를 만든다.
  • 빈 중괄호 {}는 컴프리헨션이 아니라 빈 딕셔너리다.

여러 forif의 순서

다음 컴프리헨션은 왼쪽부터 중첩 반복문으로 읽는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
pairs = [
    (x, y)
    for x in [1, 2]
    for y in [10, 20]
    if x + y >= 12
]

print(pairs)                # [(1, 20), (2, 10), (2, 20)]

동등한 일반 반복문은 다음과 같다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
pairs = []
for x in [1, 2]:
    for y in [10, 20]:
        if x + y >= 12:
            pairs.append((x, y))

forif의 순서를 임의로 바꾸면 결과가 달라질 수 있다. 출력 문제에서는 컴프리헨션을 일반 반복문 형태로 풀어 쓰면 안전하다.

컴프리헨션 변수의 범위

Python 3에서 컴프리헨션의 반복 변수는 컴프리헨션 내부 범위에 속하므로 바깥의 같은 이름을 덮어쓰지 않는다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
x = 100
values = [x * 2 for x in [1, 2, 3]]

print(x)                    # 100
print(values)               # [2, 4, 6]

일반 for 문의 반복 변수가 반복 뒤에도 남는 것과 구분한다.

예제 A — 시퀀스·슬라이스·복사

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
import copy

values = [10, 20, 30, 40, 50]
alias = values
shallow = values[:]

alias[0] = 99
shallow.append(60)

print(values)
print(shallow)
print(values is alias, values is shallow)

nested = [[1], [2]]
nested_copy = nested.copy()
nested_copy[0].append(9)
print(nested)

deep_copy = copy.deepcopy(nested)
deep_copy[0].append(8)
print(nested)
print(deep_copy)

print(values[1:5:2])
print(values[::-1])

예상 출력:

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
[99, 20, 30, 40, 50]
[10, 20, 30, 40, 50, 60]
True False
[[1, 9], [2]]
[[1, 9], [2]]
[[1, 9, 8], [2]]
[20, 40]
[50, 40, 30, 20, 99]

핵심 추적:

  1. aliasvalues와 같은 리스트를 가리킨다.
  2. shallow는 새 바깥 리스트이므로 alias[0] = 99의 영향을 받지 않는다.
  3. nested_copy는 얕은 복사이므로 내부 리스트를 공유한다.
  4. deep_copy는 내부 리스트까지 복사하므로 8 추가가 원본에 반영되지 않는다.

수치 원소를 합하는 sum

sum(iterable)은 수치 원소들을 더한 값을 반환한다. 시작값을 생략하면 0이므로 빈 입력의 합은 0이다. sum(iterable, start)는 start에 각 원소를 더한다. 예를 들어 sum([2, 4], 10)은 16이다. 여기서는 정수 원소의 합을 기준으로 계산하며 문자열 연결에 sum을 사용하지 않는다.

리스트에 중복된 값이 있으면 각각 더한다. 먼저 set으로 바꾸면 중복 제거 후의 원소들을 더하므로 합이 달라질 수 있다. 딕셔너리에 sum(d)를 적용하면 키를 순회하며, 값의 합에는 sum(d.values())를 사용한다. 여러 키가 같은 값을 가지면 values에는 그 값이 각 항목에 대해 나타난다.

sum은 리스트 원소를 수정하지 않는다. 이미 변수에 저장한 정수 합계가 나중의 리스트 변경에 따라 자동 재계산되는 것도 아니다. 컴프리헨션으로 만든 리스트를 합할 때는 반복 범위, 필터 조건, 원소 계산 순서로 실제 결과 원소를 먼저 구한다.