Karinoya 학습실

자격증 · Cloud / AI / Python 합격 랩

제어 구조와 자료 구조

문제와 해설을 한국어(으)로 읽을 수 있습니다. 강의(해설 기사)는 일본어판에만 있습니다.

일본어판(강의 포함) 보기 →

문제 1 | for/else

for문에 else절을 붙인 다음 코드를 실행했을 때 출력되는 내용은 어느 것인가?

for n in range(4):    if n == 9:        breakelse:    print("done")print(n)
  1. 3 만 출력되고 done 은 나오지 않는다
  2. done 만 출력되고 3 은 나오지 않는다
  3. 3 과 done 이 이 순서로 출력된다
  4. done 과 3 이 이 순서로 출력된다
정답D. done 과 3 이 이 순서로 출력된다

for에 붙인 else절은 반복문이 break 되지 않고 끝났을 때 실행된다. range(4) 안에 9 는 없으므로 break 는 한 번도 일어나지 않아 반복문을 끝까지 돌고 나서 else의 print가 동작해 done이 나온다. 그 뒤의 print(n) 은 반복문을 빠져나온 시점에 n에 남아 있는 마지막 값인 3을 낸다. 출력 순서를 반대로 생각하면 3이 먼저 오는 형태가 되고, else가 반복문 종료 시 반드시 건너뛰어진다고 오해하면 done이 나오지 않는 형태가 된다. n이 반복문을 빠져나온 뒤에도 남아 있다는 것을 놓치면 3이 나오지 않는 형태가 된다.

문제 2 | break와 else

break로 반복문을 빠져나왔을 때 else절이 어떻게 다뤄지는지 확인한다. 다음 코드의 출력은 어느 것인가?

for c in "abc":    if c == "b":        break    print(c)else:    print("no break")
  1. a b c 와 no break 가 모두 출력된다
  2. a 와 no break 가 이 순서로 출력된다
  3. a 와 b 두 개가 이 순서로 출력된다
  4. a 가 출력되고 no break 는 출력되지 않는다
정답D. a 가 출력되고 no break 는 출력되지 않는다

1회차는 c 가 a 이므로 print(c) 가 동작해 a 가 나온다. 2회차에 c 가 b 가 되어 break 로 반복문을 중단한다. for의 else절은 break 되지 않고 끝났을 때만 실행되는 규칙이므로 여기서는 건너뛰어져 no break는 나오지 않는다. else를 반복문 종료 시 반드시 동작하는 것으로 착각하면 no break까지 나오는 형태가 되고, break 전에 print(c) 가 있다는 것을 잘못 읽으면 b까지 나오는 형태가 된다. break 자체를 놓치면 a b c 가 모두 나오는 형태가 된다.

문제 3 | 비어 있어도 else

반복 대상이 비어 있을 때 for의 else절이 어떻게 되는지 묻는다. 다음 코드의 실행 결과는 어느 것인가?

items = []for it in items:    print(it)else:    print("finished")
  1. else절이 무시되어 TypeError 가 된다
  2. 반복이 0회이므로 StopIteration 이 된다
  3. 아무것도 출력되지 않은 채 반복문을 빠져나와 끝난다
  4. finished 가 한 번만 출력된다
정답D. finished 가 한 번만 출력된다

for의 else절이 실행되는 조건은 'break 되지 않고 반복문이 끝났다는 것'이지 '한 번 이상 반복했다는 것'이 아니다. items 가 비어 있으므로 본체는 한 번도 동작하지 않지만 break 도 하지 않았으므로 else 는 그대로 실행되어 finished 가 나온다. 비어 있으면 else도 건너뛰어진다고 생각하면 아무것도 출력되지 않는 형태가 된다. 빈 리스트를 for에 넘기는 것 자체는 올바른 작성법이므로 예외가 되지 않으며 TypeError나 StopIteration이 발생하는 일도 없다.

문제 4 | while/else

while문에 else절을 붙이고 도중에 break한 경우를 생각한다. 다음 코드의 출력은 어느 것인가?

n = 5while n > 0:    n -= 1    if n == 2:        breakelse:    print("loop ended")print(n)
  1. 0 이 출력되고 loop ended 는 출력되지 않는다
  2. 2 가 출력되고 loop ended 는 출력되지 않는다
  3. loop ended 와 2 가 이 순서로 출력된다
  4. loop ended 와 0 이 이 순서로 출력된다
정답B. 2 가 출력되고 loop ended 는 출력되지 않는다

n은 5에서 1씩 줄어 4·3으로 진행해 2가 된 시점에서 break한다. while의 else절도 for와 마찬가지로 break되지 않고 끝났을 때만 실행되므로 여기서는 건너뛰어져 loop ended는 나오지 않는다. 남는 것은 print(n) 의 2뿐이다. else가 반드시 동작한다고 생각하면 loop ended까지 나오는 형태가 되고, break를 놓쳐 조건이 거짓이 될 때까지 돈다고 잘못 읽으면 n이 0이 되는 형태가 된다.

문제 5 | continue

continue와 break를 하나의 반복문에 섞은 다음 코드를 실행했을 때 마지막에 출력되는 리스트는 어느 것인가?

out = []for i in range(6):    if i == 3:        continue    if i == 5:        break    out.append(i)print(out)
  1. [0, 1, 2, 4, 5]
  2. [0, 1, 2, 3]
  3. [0, 1, 2, 4]
  4. [0, 1, 2, 3, 4]
정답C. [0, 1, 2, 4]

i가 0·1·2일 때는 두 개의 if를 모두 통과해 append 된다. i가 3일 때 continue가 작동해 그 회차의 나머지, 즉 append가 건너뛰어진다. i가 4일 때는 다시 append 되고, i가 5일 때 break로 반복문을 중단하므로 append는 이루어지지 않는다. 따라서 [0, 1, 2, 4] 가 된다. break한 회차도 추가된다고 생각하면 5까지 나열되는 형태가 되고, continue를 pass처럼 취급하면 3이 남는 형태가 된다.

문제 6 | range의 역순

range에 음수 간격을 넘겼을 때의 나열을 묻는다. 다음 코드의 출력은 어느 것인가?

print(list(range(10, 0, -3)))
  1. [9, 6, 3, 0]
  2. [7, 4, 1, -2]
  3. [10, 7, 4, 1, 0]
  4. [10, 7, 4, 1]
정답D. [10, 7, 4, 1]

range(start, stop, step) 은 start부터 시작해 step씩 나아가 stop에 도달하기 전에 멈춘다. 여기서는 10에서 3씩 빼서 10·7·4·1로 나아가고, 다음의 -2는 0 이하이므로 채택되지 않는다. stop인 0은 포함되지 않으므로 끝에 0이 나열되는 형태는 되지 않는다. start를 포함하지 않는다고 오해하면 7부터 시작하는 형태가 되고, start를 한 개 앞부터 세기 시작한다고 생각하면 9부터 시작하는 형태가 된다. stop을 포함하지 않는다는 점은 양수 간격일 때와 같다.

문제 7 | range의 타입

내장 함수 range를 호출해 얻는 객체에 대한 설명으로 적절한 것은 어느 것인가?

  1. generator 타입 객체로, 한 번 반복하면 비게 된다
  2. range 타입 객체로, list()에 넘기면 리스트가 된다
  3. tuple 타입 객체로, 요소를 바꿔 넣을 수 없다
  4. list 타입 객체로, 그대로 대괄호가 붙은 채 표시된다
정답B. range 타입 객체로, list()에 넘기면 리스트가 된다

range가 반환하는 것은 range 타입 객체로, 필요한 정수를 그때그때 계산해서 반환한다. 리스트처럼 모든 요소를 미리 가지지 않으므로 큰 범위라도 메모리를 잡아먹지 않는다. 내용을 나열해 확인하고 싶을 때는 list()에 통과시킨다. type(range(3)) 은 range이지 list도 tuple도 아니며, 그대로 표시해도 요소의 나열이 아니라 range(0, 3) 이라고 나온다. 또한 한 번 쓰고 마는 것이 아니라 몇 번이든 반복할 수 있으므로 generator도 아니다. 참고로 첨자로 읽을 수 있고 요소를 바꿔 넣을 수 없다는 점은 튜플과 같으므로 그 성질만으로는 tuple과 구별할 수 없다.

문제 8 | 단락 평가

논리 연산자 or과 and가 무엇을 반환하는지 확인한다. 다음 코드의 출력은 어느 것인가?

print([] or 0 or "z")print(1 and 0 and 2)
  1. True 와 False 가 이 순서로 출력된다
  2. False 와 0 이 이 순서로 출력된다
  3. z 와 0 이 이 순서로 출력된다
  4. z 와 2 가 이 순서로 출력된다
정답C. z 와 0 이 이 순서로 출력된다

or은 왼쪽부터 평가해 처음으로 참이 되는 값을 그대로 반환한다. 빈 리스트도 0도 거짓이므로 마지막 "z" 가 반환된다. and는 반대로 처음으로 거짓이 되는 값을 반환하므로 1은 참, 다음의 0에서 거짓이 확정되어 0이 반환되고 맨 오른쪽의 2는 평가되지 않는다. 반환되는 것은 진리값이 아니라 평가를 멈춘 시점의 값이므로 True나 False가 나오는 일은 없다. and가 맨 오른쪽의 2를 반환하는 것은 도중이 모두 참이었을 때뿐이다.

문제 9 | 연쇄 비교

비교의 연쇄와 삼항 연산자를 조합한 다음 코드를 실행했을 때 출력되는 내용은 어느 것인가?

x = 3print(1 < x < 5)print("yes" if x % 2 else "no")
  1. True 와 yes 가 이 순서로 출력된다
  2. True 와 no 가 이 순서로 출력된다
  3. False 와 no 가 이 순서로 출력된다
  4. False 와 yes 가 이 순서로 출력된다
정답A. True 와 yes 가 이 순서로 출력된다

1 < x < 5 는 (1 < x) and (x < 5) 와 같은 의미로, x가 3이면 둘 다 성립하므로 True가 된다. 삼항 연산자는 참일 때의 값 if 조건 else 거짓일 때의 값 이라는 순서이며, 조건은 x % 2 즉 1이다. Python에서는 0이 아닌 정수는 참으로 취급되므로 yes가 선택된다. 연쇄 비교를 1 < (x < 5) 처럼 뒤에서부터 결합한다고 잘못 읽으면 x < 5 가 True 즉 1이 되어 1 < 1 로 False라는 오답이 된다. 또한 1을 거짓이라고 생각하면 no가 선택되는 형태가 된다.

문제 10 | pass의 역할

Python의 pass문에 대한 설명으로 적절한 것은 어느 것인가?

  1. 반복문 자체를 중단하고 밖으로 빠져나감을 나타낸다
  2. 문법상 블록이 필요한 곳에서 아무것도 하지 않음을 나타낸다
  3. 반복문의 나머지를 건너뛰고 다음 반복으로 진행함을 나타낸다
  4. 함수의 실행을 마치고 호출한 곳으로 값을 반환함을 나타낸다
정답B. 문법상 블록이 필요한 곳에서 아무것도 하지 않음을 나타낸다

Python은 들여쓰기로 블록을 나타내기 때문에 빈 블록을 쓸 수 없다. 클래스나 함수의 내용을 나중에 쓸 생각일 때나, 조건에 맞았을 때 일부러 아무것도 하지 않을 때, 그 자리를 채우는 것이 pass이다. 처리 흐름을 바꾸는 작용은 없다. 다음 반복으로 나아가는 것은 continue, 반복문을 중단하는 것은 break, 호출한 곳으로 값을 반환하는 것은 return이며 모두 pass와는 역할이 다르다.

문제 11 | enumerate

enumerate에 두 번째 인수를 넘긴 다음 코드를 실행했을 때 화면에 나오는 것은 어느 것인가?

for i, name in enumerate(["gu", "choki", "pa"], 1):    print(i, name)
  1. 1 gu / 2 choki / 3 pa 세 줄이 출력된다
  2. 0 gu / 1 choki / 2 pa 세 줄이 출력된다
  3. gu 1 / choki 2 / pa 3 세 줄이 출력된다
  4. 1 gu / 2 choki 두 줄만 출력된다
정답A. 1 gu / 2 choki / 3 pa 세 줄이 출력된다

enumerate는 (번호, 요소) 라는 튜플을 순서대로 반환하며, 두 번째 인수 start로 세기 시작하는 값을 지정할 수 있다. 여기서는 1을 넘겼으므로 번호는 1·2·3이 된다. start를 생략했을 때의 0부터 시작과 혼동하면 0부터 시작하는 형태가 된다. 반환되는 튜플은 번호가 먼저이고 요소가 나중이므로 print(i, name) 의 나열도 그 순서가 되며 이름이 먼저 나오는 일은 없다. 요소는 세 개 모두 꺼내지므로 두 줄에서 끊기는 일도 없다.

문제 12 | zip의 길이

길이가 다른 두 리스트를 zip에 넘겼다. 다음 코드의 실행 결과는 어느 것인가?

print(list(zip([1, 2, 3], [10, 20])))
  1. [(1, 10), (2, 20)]
  2. [(1, 10), (2, 20), (3,)]
  3. [(1, 10), (2, 20), (3, 0)]
  4. [(10, 1), (20, 2)]
정답A. [(1, 10), (2, 20)]

zip은 넘겨받은 반복 가능한 것들에서 하나씩 꺼내 짝을 짓고, 가장 짧은 것이 다한 시점에서 끝난다. 여기서는 두 번째 리스트가 2개 요소이므로 짝은 2개만 만들어지고 남은 3은 조용히 버려진다. 오류가 되지 않으며, 남는 부분이 0으로 채워지는 일도, 한쪽만 있는 튜플이 만들어지는 일도 없다. 짝의 내용은 넘긴 순서대로 나열되므로 10과 1처럼 뒤바뀌는 일도 없다.

문제 13 | zip으로 분해

짝지어진 데이터를 별표를 붙여 zip에 다시 넘겼다. 다음 코드의 출력은 어느 것인가?

pairs = [(1, 'a'), (2, 'b'), (3, 'c')]nums, chars = zip(*pairs)print(nums)print(chars)
  1. [1, 2, 3] 다음에 ['a', 'b', 'c'] 가 출력된다
  2. (1, 2, 3) 다음에 ('a', 'b', 'c') 가 출력된다
  3. ((1, 'a'),) 다음에 ((2, 'b'),) 가 출력된다
  4. (1, 'a') 다음에 (2, 'b') 가 출력된다
정답B. (1, 2, 3) 다음에 ('a', 'b', 'c') 가 출력된다

별표를 붙이면 리스트 안의 짝이 하나씩 개별 인수로 zip에 전달된다. 즉 zip((1, 'a'), (2, 'b'), (3, 'c')) 을 호출한 것과 같아져, 각 짝의 첫 번째끼리, 두 번째끼리가 모여 (1, 2, 3) 과 ('a', 'b', 'c') 두 개로 나뉜다. 짝지은 것을 원래의 두 개로 되돌리는 정석으로, zip을 두 번 통과시키면 원래대로 돌아온다. 반환되는 것은 리스트가 아니라 튜플이므로 대괄호 형태가 되지 않는다. 별표를 빼먹고 zip(pairs) 라고 쓰면 짝 그 자체가 하나의 인수로 전달되어 ((1, 'a'),) 처럼 요소가 하나인 튜플이 나열되는 형태가 된다.

문제 14 | dict와 zip

키의 나열과 값의 나열로부터 딕셔너리를 만든다. 다음 코드의 실행 결과는 어느 것인가?

keys = ["a", "b", "c"]vals = [10, 20, 30]print(dict(zip(keys, vals)))
  1. [('a', 10), ('b', 20), ('c', 30)]
  2. {10: 'a', 20: 'b', 30: 'c'}
  3. {'a', 'b', 'c', 10, 20, 30}
  4. {'a': 10, 'b': 20, 'c': 30}
정답D. {'a': 10, 'b': 20, 'c': 30}

zip(keys, vals) 는 (키, 값) 이라는 튜플을 순서대로 반환하고, dict() 는 그 짝의 나열을 받아 딕셔너리를 만든다. 짝의 첫 번째가 키, 두 번째가 값이 되므로 숫자가 키가 되는 형태는 되지 않는다. dict() 에 통과시켰으므로 튜플의 리스트인 채로 남지도 않는다. 중괄호로 표시되어도 콜론으로 짝지어져 있는 것이 딕셔너리, 값만 나열되어 있는 것이 집합이다.

문제 15 | 리스트 컴프리헨션

리스트 컴프리헨션을 사용한 다음 코드를 실행했을 때 출력되는 리스트는 어느 것인가?

print([n * n for n in range(1, 5)])
  1. [0, 1, 4, 9]
  2. [2, 4, 6, 8]
  3. [1, 4, 9, 16]
  4. [1, 4, 9, 16, 25]
정답C. [1, 4, 9, 16]

range(1, 5) 가 반환하는 것은 1·2·3·4 로, stop에 지정한 5는 포함되지 않는다. 각각을 제곱하면 1·4·9·16 이 된다. range를 0부터 시작한다고 착각하면 0부터 나열되는 형태가 되고, stop을 포함한다고 오해하면 25까지 나열되는 형태가 된다. n * n 을 n * 2 로 잘못 읽으면 2씩 늘어나는 형태가 된다. 컴프리헨션은 왼쪽 끝의 식을 각 요소에 대해 평가해 그 결과를 나열한 새로운 리스트를 만든다는 읽는 법을 익혀 두어야 한다.

문제 16 | 딕셔너리 컴프리헨션

중괄호 안에 콜론을 쓴 컴프리헨션을 실행한다. 다음 코드의 출력은 어느 것인가?

words = ["sun", "moon"]print({w: len(w) for w in words})
  1. {'sun': 3, 'moon': 4}
  2. [('sun', 3), ('moon', 4)]
  3. {3: 'sun', 4: 'moon'}
  4. {'sun', 'moon', 3, 4}
정답A. {'sun': 3, 'moon': 4}

중괄호 안에 콜론이 있는 컴프리헨션은 딕셔너리 컴프리헨션으로, 콜론 왼쪽이 키, 오른쪽이 값이 된다. 여기서는 단어를 키, 그 글자 수를 값으로 하는 딕셔너리가 만들어진다. 키와 값을 반대로 읽으면 글자 수가 키가 되는 형태가 된다. 대괄호로 감싸면 튜플의 리스트가 되지만 여기서는 중괄호이므로 그렇게 되지 않는다. 콜론이 없으면 집합 컴프리헨션이 되어 값만 나열된 표시가 되지만 콜론이 있는 이상 만들어지는 것은 딕셔너리이다.

문제 17 | 소괄호의 타입

컴프리헨션 같은 식을 소괄호로 감쌌다. 다음 코드를 실행했을 때 표시되는 타입은 어느 것인가?

g = (n for n in range(3))print(type(g))
  1. <class 'range'>
  2. <class 'list'>
  3. <class 'generator'>
  4. <class 'tuple'>
정답C. <class 'generator'>

Python에는 튜플 컴프리헨션이라는 표기법이 없어서 소괄호로 감싼 이 형태는 제너레이터 식이 된다. 타입은 generator이며, 값을 미리 전부 만들지 않고 필요할 때 하나씩 계산하므로 메모리를 절약하지만 이터레이터이므로 한 번 쓰면 다한다. 튜플을 원할 때는 tuple() 로 감싼다. 대괄호로 쓰면 리스트 컴프리헨션이 되어 list가 되고, range(3) 을 그대로 대입하면 range가 된다. tuple은 이 표기법으로는 얻을 수 없다.

문제 18 | 집합 컴프리헨션

집합 컴프리헨션으로 만든 그릇의 요소 수를 센다. 다음 코드의 출력은 어느 것인가?

print(len({n % 4 for n in range(10)}))
  1. 3 이 출력된다
  2. 0 이 출력된다
  3. 10 이 출력된다
  4. 4 가 출력된다
정답D. 4 가 출력된다

range(10) 의 각 값을 4로 나눈 나머지는 0·1·2·3·0·1·2·3·0·1 이 된다. 중괄호에 콜론이 없는 컴프리헨션은 집합 컴프리헨션이므로 중복된 값은 하나로 합쳐져 남는 것은 0·1·2·3의 4종류이다. 중복이 사라진다는 것을 놓치고 값의 개수를 그대로 세면 10이 된다. 나머지에 0이 포함된다는 것을 놓치고 1·2·3의 3종류로 세면 3이 된다. 0은 컴프리헨션이 아무것도 만들지 않는다고 오해한 경우의 값이지만 range(10) 을 도는 한 내용이 비는 일은 없다. 집합의 표시 순서는 정해져 있지 않으므로 요소 수로 확인하는 것이 확실하다.

문제 19 | 이중 컴프리헨션

for를 두 개 나열한 컴프리헨션의 나열 순서를 묻는다. 다음 코드의 실행 결과는 어느 것인가?

print([(x, y) for x in [1, 2] for y in "ab"])
  1. [(1, 'a'), (2, 'b'), (1, 'b'), (2, 'a')]
  2. [('a', 1), ('b', 1), ('a', 2), ('b', 2)]
  3. [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
  4. [(1, 'a'), (2, 'a'), (1, 'b'), (2, 'b')]
정답C. [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]

컴프리헨션에 for를 두 개 나열했을 때는 왼쪽에 쓴 for가 바깥쪽 반복문이 된다. 일반적인 for문을 중첩해서 쓴 경우와 완전히 같은 순서로, x를 1로 고정하고 y를 a·b로 돌린 다음 x를 2로 해서 같은 것을 반복한다. 오른쪽 for를 바깥쪽이라고 읽으면 x가 먼저 도는 순서가 된다. 짝의 내용은 식에 쓴 (x, y) 의 나열대로이므로 문자가 먼저 오는 일은 없다.

문제 20 | start 인수

내장 함수 enumerate의 두 번째 인수 start에 대한 설명으로 적절한 것은 어느 것인가?

  1. 세기 시작하는 번호를 지정한다. 생략하면 0부터 센다
  2. 어느 요소부터 꺼낼지를 지정한다. 생략하면 맨 앞부터 가져온다
  3. 몇 개씩 건너뛸지를 지정한다. 생략하면 하나씩 나아간다
  4. 몇 번째까지 셀지를 지정한다. 생략하면 마지막까지 센다
정답A. 세기 시작하는 번호를 지정한다. 생략하면 0부터 센다

enumerate의 두 번째 인수 start는 붙이는 번호를 몇부터 시작할지만을 정한다. 기본값은 0이므로 1을 넘기면 사람이 보기 편한 일련번호가 된다. 번호 붙이는 방식만 바뀔 뿐 꺼내지는 요소의 범위나 개수는 바뀌지 않는다는 점이 핵심이다. 개수를 제한하거나 건너뛰는 작용은 없으며 꺼내기 시작하는 위치를 옮기는 것도 아니다. 이것들은 모두 슬라이스 등 다른 도구에 대한 설명이다.

문제 21 | sort의 반환값

리스트의 sort 메서드의 반환값을 변수로 받았다. 다음 코드의 출력은 어느 것인가?

nums = [5, 3, 9]result = nums.sort()print(result)print(nums)
  1. [3, 5, 9] 와 [3, 5, 9] 가 이 순서로 출력된다
  2. None 과 [5, 3, 9] 가 이 순서로 출력된다
  3. [3, 5, 9] 와 [5, 3, 9] 가 이 순서로 출력된다
  4. None 과 [3, 5, 9] 가 이 순서로 출력된다
정답D. None 과 [3, 5, 9] 가 이 순서로 출력된다

sort는 리스트를 그 자리에서 정렬하는 메서드로 반환값은 None이다. 따라서 result에는 None이 들어가고 정렬된 결과는 nums 쪽에 남는다. 정렬된 새 리스트가 반환된다고 착각하면 result 쪽에 리스트가 들어가는 형태가 된다. sort가 원래 리스트를 바꾼다는 것을 놓치면 nums가 원래 순서 그대로인 형태가 된다. 원본을 남긴 채 정렬된 리스트를 원할 때는 내장 함수 sorted를 쓴다.

문제 22 | sorted의 차이

리스트의 sort 메서드와 내장 함수 sorted의 차이로 적절한 것은 어느 것인가?

  1. sort는 새로운 리스트를 반환하고, sorted는 그 자리에서 정렬해 None을 반환한다
  2. sort는 그 자리에서 정렬해 None을 반환하고, sorted는 새로운 리스트를 반환한다
  3. 둘 다 새로운 리스트를 반환하며 원래 리스트는 어느 쪽도 바뀌지 않는다
  4. 둘 다 그 자리에서 정렬한 뒤 정렬된 리스트를 반환한다
정답B. sort는 그 자리에서 정렬해 None을 반환하고, sorted는 새로운 리스트를 반환한다

sort는 리스트의 메서드로 그 리스트 자신을 정렬하며 반환값으로는 None을 반환한다. sorted는 내장 함수로 원래 순서를 전혀 바꾸지 않고 정렬된 새 리스트를 반환한다. 원본을 남기고 싶은지 여부로 선택해 쓴다. 둘의 역할을 바꿔서 기억하면 sort가 새 리스트를 반환한다는 설명이 된다. 파괴적인지 여부를 한쪽으로 몰아 생각하면 둘 다 그 자리에서 정렬한다거나 둘 다 원본을 바꾸지 않는다는 설명이 된다. 참고로 sorted는 리스트 이외의 반복 가능한 것도 받을 수 있으며 결과는 항상 리스트가 된다.

문제 23 | append의 차이

리스트에 다른 리스트를 더하는 두 가지 방법을 비교한다. 다음 코드의 출력은 어느 것인가?

a = [1, 2]a.append([3, 4])b = [1, 2]b.extend([3, 4])print(a, b)
  1. [1, 2, [3, 4]] [1, 2, [3, 4]]
  2. [1, 2, 3, 4] [1, 2, [3, 4]]
  3. [1, 2, 3, 4] [1, 2, 3, 4]
  4. [1, 2, [3, 4]] [1, 2, 3, 4]
정답D. [1, 2, [3, 4]] [1, 2, 3, 4]

append는 인수를 통째로 하나의 요소로 끝에 더하므로 리스트를 넘기면 중첩되어 길이가 1만 늘어난다. extend는 인수를 반복해 꺼낸 값을 하나씩 더하므로 내용물이 풀려서 길이가 2 늘어난다. 두 역할을 바꿔 생각하면 a와 b의 결과가 반대가 된다. 둘 다 같은 작동이라고 생각하면 둘 다 중첩되는 형태나 둘 다 평평해지는 형태가 된다. 어느 메서드든 반환값은 None이므로 결과를 변수로 받아서는 안 된다는 점도 함께 기억해 두어야 한다.

문제 24 | 얕은 복사

중첩된 리스트를 슬라이스로 복제해 안쪽을 고쳐 썼다. 다음 코드의 실행 결과는 어느 것인가?

orig = [[1, 2], [3, 4]]copy1 = orig[:]copy1[0].append(9)print(orig)
  1. [[1, 2, 9], [3, 4, 9]]
  2. [[1, 2, 9], [3, 4]]
  3. [[1, 2], [3, 4]]
  4. [[1, 2], [3, 4], 9]
정답B. [[1, 2, 9], [3, 4]]

슬라이스에 의한 복제는 얕은 복사로, 바깥쪽 리스트는 새로 만들어지지만 안에 든 요소는 원본과 같은 것을 공유한다. copy1[0] 과 orig[0] 은 같은 리스트를 가리키고 있으므로 거기에 append하면 원본 쪽에도 보인다. 완전히 분리된다고 착각하면 원본이 바뀌지 않는 형태가 된다. 고쳐 쓴 것은 맨 앞의 안쪽 리스트뿐이므로 뒤쪽 요소까지 늘어나지 않으며, 바깥쪽에 더한 것도 아니므로 9가 바깥쪽에 나열되지도 않는다. 안쪽까지 분리하려면 copy.deepcopy를 쓴다.

문제 25 | 깊은 복사

깊은 복사와 얕은 복사를 하나의 코드로 비교한다. 실행 후 base는 어떻게 되는가?

import copybase = [[1, 2], [3, 4]]d = copy.deepcopy(base)s = base[:]d[0].append(8)s[1].append(7)print(base)
  1. [[1, 2], [3, 4]]
  2. [[1, 2, 8], [3, 4, 7]]
  3. [[1, 2, 8], [3, 4]]
  4. [[1, 2], [3, 4, 7]]
정답D. [[1, 2], [3, 4, 7]]

copy.deepcopy는 중첩된 내용물까지 복제하므로 d[0] 은 base[0] 과는 다른 리스트가 되어 거기에 대한 append는 base에 닿지 않는다. 한편 s는 얕은 복사이므로 s[1] 과 base[1] 은 같은 리스트이며 거기에 대한 append는 base에도 보인다. 따라서 뒤쪽 요소만 늘어난다. 둘 다 닿는다고 생각하면 안쪽 둘 다 늘어나는 형태가 되고, 둘 다 닿지 않는다고 생각하면 원래 그대로인 형태가 된다. 깊은 복사와 얕은 복사를 혼동하면 앞쪽 요소만 늘어나는 형태가 된다.

문제 26 | 곱셈의 함정

리스트의 곱셈으로 2차원 표를 만들려고 했다. 다음 코드의 실행 결과는 어느 것인가?

grid = [[0] * 3] * 2grid[0][1] = 5print(grid)
  1. [[0, 0, 0], [0, 5, 0]]
  2. [[5, 0, 0], [5, 0, 0]]
  3. [[0, 5, 0], [0, 0, 0]]
  4. [[0, 5, 0], [0, 5, 0]]
정답D. [[0, 5, 0], [0, 5, 0]]

바깥쪽 곱셈은 새로운 행을 만드는 것이 아니라 같은 하나의 리스트에 대한 참조를 두 개 나열하는 것뿐이다. grid[0] 과 grid[1] 은 같은 실체이므로 한쪽의 첨자 1 위치를 고쳐 쓰면 다른 쪽에서도 같은 위치가 바뀌어 보인다. 행이 독립되어 있다고 생각하면 한쪽 행만 바뀌는 형태가 된다. 고쳐 쓴 것은 첨자 1 위치이므로 맨 앞이 5가 되는 형태도 되지 않는다. 행마다 독립시키려면 [[0] * 3 for _ in range(2)] 라고 쓴다.

문제 27 | 요소 1개짜리 튜플

소괄호로 감싸기만 한 값과 끝에 쉼표를 둔 값의 타입을 비교한다. 다음 코드의 출력은 어느 것인가?

x = (7)y = (7,)print(type(x).__name__, type(y).__name__)
  1. int 와 int 가 이 순서로 출력된다
  2. tuple 과 tuple 이 이 순서로 출력된다
  3. tuple 과 int 가 이 순서로 출력된다
  4. int 와 tuple 이 이 순서로 출력된다
정답D. int 와 tuple 이 이 순서로 출력된다

튜플을 결정하는 것은 소괄호가 아니라 쉼표이다. (7) 은 계산의 우선순위를 나타내는 괄호로 감싸기만 한 정수이므로 타입은 int가 되고, (7,) 처럼 끝에 쉼표를 두어야 비로소 요소 1개짜리 튜플이 된다. 괄호로 감싸면 튜플이 된다고 착각하면 둘 다 tuple이 되는 형태가 되고, 둘을 혼동하면 순서가 반대인 형태나 둘 다 int인 형태가 된다. 참고로 빈 튜플만은 예외로, 쉼표 없이 () 라고 쓴다.

문제 28 | 튜플 속의 내용

튜플 안에 넣은 리스트에 요소를 더하려고 했다. 다음 코드의 실행 결과는 어느 것인가?

t = (1, [2, 3], 4)t[1].append(5)print(t)
  1. TypeError 가 발생한다
  2. (1, [2, 3], 4, 5)
  3. (1, [2, 3, 5], 4)
  4. (1, [2, 3], 4)
정답C. (1, [2, 3, 5], 4)

튜플이 얼려 두는 것은 어느 요소를 가리키는지에 대한 대응 관계이지, 가리키는 곳의 내용물까지 얼리는 것은 아니다. t[1] 은 리스트이고 그 리스트 자체는 가변(mutable)이므로 append할 수 있다. 튜플의 요소 자체에 대입하는 t[1] = [] 같은 표현이라면 TypeError가 되지만 여기서는 요소를 바꿔 넣지 않았다. 더한 것은 리스트 안이므로 튜플의 길이가 늘어나는 일도, 전혀 변하지 않는 채로 끝나는 일도 없다.

문제 29 | 슬라이스 대입

리스트의 슬라이스에 길이가 다른 리스트를 대입했다. 다음 코드의 출력은 어느 것인가?

x = [1, 2, 3, 4, 5]x[1:3] = [9]print(x)
  1. [1, 9, 4, 5]
  2. [1, 9, 3, 4, 5]
  3. [1, 9, 9, 4, 5]
  4. [1, 2, 9, 4, 5]
정답A. [1, 9, 4, 5]

슬라이스에 대한 대입은 지정한 범위를 통째로 우변의 내용으로 바꾸는 연산이다. x[1:3] 은 2 와 3 두 요소를 가리키므로 그것이 9 라는 하나의 요소로 바뀌어 전체 길이는 5에서 4로 줄어든다. 요소 하나만 바뀐다고 생각하면 길이가 5 그대로인 형태가 되고, 우변이 폭에 맞춰 늘어난다고 생각하면 9 가 두 개 나열되는 형태가 된다. 범위의 시작을 하나 어긋나게 읽으면 2 가 남는 형태가 된다. 좌변의 폭과 우변의 길이가 달라도 되는 것이 슬라이스 대입의 특징이다.

문제 30 | 삽입과 삭제

폭 0인 슬라이스에 대한 대입과 del을 이어서 수행한다. 다음 코드의 실행 결과는 어느 것인가?

y = [1, 2, 3]y[1:1] = [7, 8]del y[0]print(y)
  1. [7, 8, 3]
  2. [1, 7, 8, 3]
  3. [7, 8, 2, 3]
  4. [2, 3, 7, 8]
정답C. [7, 8, 2, 3]

y[1:1] 은 폭 0인 범위이므로 거기에 대한 대입은 삭제를 동반하지 않는 삽입이 되어 리스트는 [1, 7, 8, 2, 3] 이 된다. 이어지는 del y[0] 은 맨 앞의 1 을 제거하므로 [7, 8, 2, 3] 이 남는다. 삽입할 때 원래 요소도 하나 사라진다고 읽으면 7 8 3 형태가 되고, del이 다른 위치를 지운다고 생각하면 맨 앞의 1이 남는 형태가 된다. 끝에 더해졌다고 잘못 읽으면 2와 3이 앞으로 오는 형태가 된다. del은 문(statement)이라 반환값이 없고 값을 꺼내지도 않고 삭제한다는 점도 기억해 두어야 한다.

문제 31 | get의 기본값

딕셔너리에 없는 키를 get으로 조회했다. 다음 코드를 실행했을 때 출력되는 내용은 어느 것인가?

d = {"a": 1, "b": 2}print(d.get("z"), d.get("z", 0))
  1. None 과 0 이 이 순서로 출력된다
  2. 첫 번째에서 KeyError 가 발생한다
  3. None 과 None 이 이 순서로 출력된다
  4. 0 과 0 이 이 순서로 출력된다
정답A. None 과 0 이 이 순서로 출력된다

get은 키가 없을 때 예외를 던지지 않고, 두 번째 인수를 생략했다면 None을, 넘겼다면 그 값을 반환한다. 따라서 첫 번째는 None, 두 번째는 0이 된다. 두 번째 인수가 없을 때도 0이 반환된다고 생각하면 둘 다 0이 되고, 두 번째 인수가 무시된다고 생각하면 둘 다 None이 된다. 대괄호로 d["z"] 라고 쓰면 KeyError가 되는데, 그것을 피하기 위한 도구가 get이다.

문제 32 | setdefault

setdefault를 이미 있는 키와 없는 키 양쪽에 대해 호출했다. 다음 코드의 출력은 어느 것인가?

d = {"a": 1}print(d.setdefault("a", 99))print(d.setdefault("b", 99))print(d)
  1. 99 와 99 다음에 {'a': 99, 'b': 99} 가 출력된다
  2. 1 과 99 다음에 {'a': 1, 'b': 99} 가 출력된다
  3. 1 과 None 다음에 {'a': 1, 'b': None} 이 출력된다
  4. 1 과 99 다음에 {'a': 1} 이 출력된다
정답B. 1 과 99 다음에 {'a': 1, 'b': 99} 가 출력된다

setdefault는 키가 있으면 기존 값을 반환하기만 하고 딕셔너리를 바꾸지 않는다. 키가 없으면 두 번째 인수를 값으로 삽입하고 그 값을 반환한다. 따라서 첫 번째 호출은 기존의 1이 반환되어 딕셔너리는 그대로이고, 두 번째 호출은 99가 삽입되어 99가 반환된다. 기존 값까지 덮어써진다고 생각하면 둘 다 99가 되는 형태가 되고, 삽입이 일어나지 않는다고 생각하면 딕셔너리가 늘어나지 않는 형태가 된다. 두 번째 인수를 생략하면 삽입되는 값은 None이 되므로 None이 들어가는 형태는 두 번째 인수를 쓰지 않은 경우의 결과이다.

문제 33 | in이 보는 곳

딕셔너리에 대한 in 연산자가 무엇을 조사하는지 확인한다. 다음 코드의 출력은 어느 것인가?

d = {"x": 1, "y": 2}print("x" in d, 1 in d, 1 in d.values())
  1. True False True 가 이 순서로 출력된다
  2. True True True 가 이 순서로 출력된다
  3. True False False 가 이 순서로 출력된다
  4. False True True 가 이 순서로 출력된다
정답A. True False True 가 이 순서로 출력된다

딕셔너리에 대한 in은 키만 조사한다. "x"는 키에 있으므로 True, 1은 값으로는 존재하지만 키에는 없으므로 False가 된다. 값을 조사하고 싶을 때는 d.values() 를 대상으로 하면 되며 이쪽은 True가 된다. in이 값도 본다고 생각하면 전부 True가 되고, values()를 대상으로 해도 값을 보지 않는다고 생각하면 마지막이 False가 된다. 키와 값의 역할을 바꿔서 읽으면 첫 번째가 False가 된다. 키의 유무를 확인한 뒤 대괄호로 조회한다는 사용법이 정석이다.

문제 34 | 키의 중복

딕셔너리 리터럴 안에 같은 키를 두 번 썼다. 다음 코드의 실행 결과는 어느 것인가?

d = {"a": 1, "b": 2, "a": 3}print(d)
  1. {'a': 3, 'b': 2}
  2. {'a': 1, 'b': 2}
  3. {'b': 2, 'a': 3}
  4. {'a': 3, 'b': 3}
정답A. {'a': 3, 'b': 2}

딕셔너리 리터럴은 왼쪽부터 순서대로 항목을 만들어 가므로 같은 키가 두 번 나타나면 나중 값이 앞의 값을 덮어쓴다. 오류나 경고가 되지 않는다. 먼저 쓴 쪽이 남는다고 생각하면 a의 값이 1인 채로인 형태가 된다. 또한 딕셔너리는 삽입한 순서를 유지하며 덮어쓰기로 위치가 이동하지 않으므로 a가 먼저 오는 나열 그대로이다. 따라서 키의 순서가 뒤바뀌는 일도, b의 값까지 덩달아 바뀌는 일도 없다.

문제 35 | 딕셔너리 만드는 법

키워드 인수로 만든 딕셔너리와 짝의 나열로 만든 딕셔너리를 비교한다. 다음 코드의 출력은 어느 것인가?

d1 = dict(x=1, y=2)d2 = dict([("x", 1), ("y", 2)])print(d1 == d2)print(d1)
  1. False 다음에 {'x': 1, 'y': 2} 가 출력된다
  2. True 다음에 {1: 'x', 2: 'y'} 가 출력된다
  3. True 다음에 {'x': 1, 'y': 2} 가 출력된다
  4. True 다음에 [('x', 1), ('y', 2)] 가 출력된다
정답C. True 다음에 {'x': 1, 'y': 2} 가 출력된다

dict는 키워드 인수로부터도, (키, 값) 짝의 나열로부터도 딕셔너리를 만들 수 있다. 어느 쪽 방식으로 써도 같은 내용의 딕셔너리가 만들어지므로 비교는 True가 된다. 만드는 방식이 다르면 다른 것이 된다고 생각하면 비교가 False가 된다. 키워드 인수의 이름이 그대로 키의 문자열이 되므로 키와 값이 뒤바뀌는 일은 없다. dict에 통과시킨 시점에 딕셔너리가 되어 있으므로 짝의 나열인 채로 남지도 않는다.

문제 36 | 빈 중괄호

빈 중괄호와 set()의 타입을 비교한다. 다음 코드를 실행했을 때 표시되는 두 타입 이름은 어느 것인가?

a = {}b = set()print(type(a).__name__, type(b).__name__)
  1. dict 와 dict 가 이 순서로 출력된다
  2. set 과 set 이 이 순서로 출력된다
  3. dict 와 set 이 이 순서로 출력된다
  4. set 과 dict 가 이 순서로 출력된다
정답C. dict 와 set 이 이 순서로 출력된다

요소가 든 {1, 2} 는 집합이 되지만 내용이 비어 있으면 딕셔너리인지 집합인지 정할 수 없다. Python은 이 경우를 딕셔너리로 다루므로 {} 는 빈 딕셔너리이다. 빈 집합을 원할 때는 set() 이라고 쓸 수밖에 없다. 중괄호면 항상 집합이라고 생각하면 둘 다 set이 되고, 둘을 혼동하면 순서가 반대가 된다. set()까지 딕셔너리가 된다고 생각하면 둘 다 dict가 된다. 집합과 딕셔너리는 둘 다 중괄호를 쓰기 때문에 이 구분은 혼동하기 쉽다.

문제 37 | 집합 연산

두 집합에 교집합과 대칭차 연산을 수행했다. 다음 코드의 출력은 어느 것인가?

s1 = {1, 2, 3, 4}s2 = {3, 4, 5}print(sorted(s1 & s2), sorted(s1 ^ s2))
  1. [3, 4] 와 [1, 2, 3, 4, 5] 가 이 순서로 출력된다
  2. [1, 2, 5] 와 [3, 4] 가 이 순서로 출력된다
  3. [3, 4] 와 [1, 2] 가 이 순서로 출력된다
  4. [3, 4] 와 [1, 2, 5] 가 이 순서로 출력된다
정답D. [3, 4] 와 [1, 2, 5] 가 이 순서로 출력된다

앰퍼샌드는 교집합으로 양쪽에 다 있는 3과 4가 남는다. 캐럿은 대칭차로 어느 한쪽에만 있는 요소를 모으므로 s1에만 있는 1과 2, s2에만 있는 5가 남고 공통인 3과 4는 빠진다. 대칭차를 차집합과 혼동하면 5가 빠진 형태가 되고, 두 연산을 바꾸면 출력 순서가 반대가 된다. 대칭차를 합집합과 혼동하면 3과 4까지 포함하는 형태가 된다. 집합의 표시 순서는 정해져 있지 않으므로 여기서는 sorted로 나열해 확인하고 있다.

문제 38 | 중복 제거

중복이 있는 리스트를 집합에 통과시켰다. 다음 코드를 실행했을 때의 출력은 어느 것인가?

data = [3, 1, 3, 2, 1, 1]print(len(set(data)), sorted(set(data)))
  1. 6 과 [3, 1, 3, 2, 1, 1] 이 이 순서로 출력된다
  2. 6 과 [1, 2, 3] 이 이 순서로 출력된다
  3. 3 과 [3, 1, 2] 가 이 순서로 출력된다
  4. 3 과 [1, 2, 3] 이 이 순서로 출력된다
정답D. 3 과 [1, 2, 3] 이 이 순서로 출력된다

set에 리스트를 넘기면 중복이 제거되어 남는 값은 1·2·3의 3종류이므로 길이는 3이다. sorted에 집합을 넘기면 오름차순으로 정렬된 새 리스트가 반환된다. 중복이 남는다고 생각하면 개수가 6이 되는 형태가 된다. sorted는 원래 등장 순서가 아니라 크기 순으로 정렬하므로 처음 나타난 순서대로 나열되지도 않는다. 집합 자체의 표시 순서에는 의미가 없으므로 나열하고 싶을 때는 sorted를 통과시킨다.

문제 39 | 요소의 조건

집합에 튜플과 리스트를 순서대로 추가하려고 했다. 다음 코드를 실행하면 어떻게 되는가?

s = set()s.add((1, 2))s.add([3, 4])print(len(s))
  1. 1 이 출력되며 리스트는 튜플로 변환된다
  2. TypeError 가 발생하며 리스트는 집합에 넣을 수 없다
  3. ValueError 가 발생하며 요소의 타입이 섞일 수 없다
  4. 2 가 출력되며 두 요소 모두 집합에 들어간다
정답B. TypeError 가 발생하며 리스트는 집합에 넣을 수 없다

집합의 요소와 딕셔너리의 키는 해시 가능해야 한다. 튜플은 내용을 바꿀 수 없으므로 해시 가능해 추가할 수 있지만, 리스트는 나중에 내용이 바뀔 수 있어 해시 가능하지 않으므로 unhashable type이라는 메시지의 TypeError가 된다. 둘 다 들어간다고 생각하면 2가 출력되는 형태가 되고, 암묵적으로 튜플로 변환된다고 생각하면 1이 출력되는 형태가 된다. 타입이 섞이는 것 자체는 문제가 아니므로 ValueError가 된다는 설명도 잘못이다.

문제 40 | 세 가지 꺼내기

딕셔너리의 메서드 keys values items가 각각 무엇을 반환하는지의 조합으로 적절한 것은 어느 것인가?

  1. keys는 값의 짝, values는 키의 짝, items는 항목 수를 반환한다
  2. keys는 키와 값의 짝, values는 키, items는 값을 반환한다
  3. keys는 값, values는 키, items는 값끼리의 짝을 반환한다
  4. keys는 키, values는 값, items는 키와 값의 짝을 반환한다
정답D. keys는 키, values는 값, items는 키와 값의 짝을 반환한다

keys는 키만, values는 값만, items는 (키, 값) 이라는 튜플을 순서대로 반환한다. 딕셔너리를 돌 때는 for k, v in d.items() 처럼 두 변수로 받는 것이 정형이다. 세 역할을 바꿔서 기억하면 keys가 값을 반환한다는 식의 설명이 된다. items가 반환하는 것은 항목의 나열이지 개수가 아니므로 항목 수를 반환한다는 설명도 잘못이며, 개수를 알고 싶을 때는 len을 쓴다. 참고로 딕셔너리를 그대로 for에 넘기면 키가 꺼내진다.

연습: 이 페이지의 문제 풀기

무작위로 출제하는 연습 도구입니다(JavaScript가 활성화된 경우에 작동합니다). 위의 문제와 해설은 그대로 모두 읽을 수 있습니다.

※ 해설은 학습용 정보 제공입니다. 시험 출제 범위와 제도는 연도에 따라 바뀌므로, 반드시 시행 기관의 공식 발표를 확인하십시오.

이 페이지는 일본어 원문을 번역한 것입니다. 번역과 원문의 내용이 다를 경우 일본어판이 우선합니다. 일본어 원문 보기