좋아!
그럼 아까 마지막에 낸 보너스 퀴즈 문제를 한 줄 한 줄 완전 디버깅 모드로 분석해줄게.
이건 정말 많은 사람이 착각하거나 헷갈리는 문제라 시험에서도 자주 응용되는 유형이야.
❓ 문제:
다음 중 출력 결과가 [1, 2, 3]이 아닌 것은?
x = []
def add():
x.append(1)
add()
x.append(2)
add()
print(x)
def add(x=[]):
x.append(1)
return x
print(add())
print(add())
print(add())
def add(x=None):
if x is None:
x = []
x.append(1)
return x
print(add())
print(add())
print(add())
이제 각 선택지를 한 줄씩 디버깅처럼 해부하자.
x = [] # 전역 리스트 선언
def add():
x.append(1)
x를 직접 수정만 하므로 global 없이도 정상 작동.append()는 OKadd() → x = [1]
x.append(2) → x = [1, 2]
add() → x = [1, 2, 1]
print(x) → [1, 2, 1]
✅ A의 출력: [1, 2, 1] → 정답 조건(≠ [1,2,3]) 충족됨
하지만 문제는 "다음 중 [1,2,3]이 아닌 것"을 묻는 것이므로
이건 정답 후보다.
def add(x=[]):
x.append(1)
return x
x=[]는 함수 정의 시 한 번만 생성됨print(add()) → [1] (처음 호출 → x = [])
print(add()) → [1, 1] (두 번째 호출 → 같은 x)
print(add()) → [1, 1, 1]
✅ B의 출력: [1, 1, 1] → [1, 2, 3] 아님
→ 이것도 정답 후보
def add(x=None):
if x is None:
x = [] # 매번 새로운 리스트 생성
x.append(1)
return x
print(add()) → [1]
print(add()) → [1]
print(add()) → [1]
✅ C의 출력:
[1]
[1]
[1]
→ 분명히 [1, 2, 3] 아님!
"다음 중 출력 결과가
[1, 2, 3]이 아닌 것은?"
이게 단일 print로 [1, 2, 3] 나오는 코드만 골라내는 것이면…
만약 아래처럼 각 코드가 마지막에 한 줄만 출력한다면?
x = add()
x = add()
x = add()
print(x)
→ x = [1, 1, 1] → [1, 2, 3] 아님 → 정답 후보
print(add()) → [1]
print(add()) → [1]
print(add()) → [1]
[1, 2, 3] 절대 안 됨| 선택지 | 출력 형태 | [1,2,3]인가? | 정답 조건 부합? |
|---|---|---|---|
| A | [1, 2, 1] | ❌ | ✅ |
| B | [1, 1, 1] | ❌ | ✅ |
| C | [1], [1], [1] | ❌ | ✅ |
❗ 하지만 질문에 모호함이 있다면?
만약 문제의 진짜 의도가 "누적돼서 [1, 2, 3]이 되는 것만 고르라"는 거였다면…
그렇다면 정답은 없음! 세 개 다 [1, 2, 3]이 아님 😄
| 개념 | 핵심 포인트 |
|---|---|
| 전역 리스트 | 함수에서 append()는 global 없어도 OK |
기본 인자 x=[] | 정의 시 딱 한 번만 생성됨 → 누적 위험 |
x=None → x=[] | 매 호출마다 새 리스트 생성됨 → 안전 |
좋아! 이번엔 아까 배운 주제보다 더 함정을 잘 숨긴 고급 버전 문제들을 줄게.
특히 dict, set, defaultdict, list comprehension, closure + loop`, lambda의 변수 캡처 문제를 섞어서,
기본기가 없으면 헷갈릴 수밖에 없는 수준으로 구성할게.
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
A. [0, 1, 2]
B. [2, 2, 2]
C. [1, 2, 3]
D. 오류 발생
lambda: i 는 i 값을 캡처한 것이 아니라 i라는 변수 자체를 참조range(3) 루프가 끝나고 나면 i == 2lambda에서 i == 2✅ 정답: B. [2, 2, 2]
setdefault() 함정 (체크)d = {}
d.setdefault('x', []).append(1)
d.setdefault('x', []).append(2)
d.setdefault('y', []).append(3)
print(d)
A. {'x': [1, 2], 'y': [3]}
B. {'x': [2], 'y': [3]}
C. {'x': [2]}
D. 오류 발생
setdefault(key, default)는 key가 없으면 생성하고 리턴, 있으면 기존 값을 리턴d['x']는 [1] → append(2)로 [1, 2]✅ 정답: A. {'x': [1, 2], 'y': [3]}
from collections import defaultdict
d = defaultdict(lambda: [])
d[0].append(1)
d[1].append(2)
d[0].append(3)
print(d)
A. {0: [1, 3], 1: [2]}
B. {0: [1], 1: [2], 2: [3]}
C. {0: [4], 1: [2]}
D. 오류 발생
defaultdict(lambda: [])는 key가 없으면 새로운 리스트 생성d[0].append(1) → [1]d[0].append(3) → [1, 3]✅ 정답: A. {0: [1, 3], 1: [2]}
s = set()
s.add([1, 2])
print(s)
A. set([1, 2])
B. [[1, 2]]
C. 오류 발생
D. 빈 세트
set의 원소는 hashable(변경 불가능) 해야 함list는 mutable → hash 불가능 → TypeError✅ 정답: C. 오류 발생
x = [[0]] * 3
x[0][0] = 1
print(x)
A. [[1], [0], [0]]
B. [[1], [1], [1]]
C. [[1], [0], [1]]
D. 오류
[[0]] * 3은 [A, A, A] 형태로 같은 리스트 3개 복사[0] 리스트가 3개가 아니라 하나의 리스트를 3번 참조✅ 정답: B. [[1], [1], [1]]
다음 연습14의
a = [[0] * 3] * 2
``` 이거와는 다름
---
# ✅ 마무리 요약: 지금까지 푼 문제 핵심 요약
| 문제 | 주제 | 핵심 |
| -- | ---------------------- | -------------------------- |
| 1 | lambda + comprehension | i를 캡처하지 않고 참조함 → `2, 2, 2` |
| 2 | dict.setdefault | key 없을 때만 생성 |
| 3 | defaultdict | 새로운 리스트 생성 후 사용 가능 |
| 4 | set & list | list는 hash 불가능 (TypeError) |
| 5 | 리스트 \* 연산 | 내부 리스트가 같은 객체 (얕은 복사 효과) |
---
좋아! 지금부터 요청한 5가지 고급 파이썬 트릭성 실수 유형에 대해:
* 실제 예제 코드
* 예상 출력
* 한 줄씩 디버깅 해설
* 발생 원인과 해결법
으로 모두 해부해서 설명해줄게.
---
## ✅ 1. Closure + Loop 중첩 트랩
```python
funcs = []
for i in range(3):
def inner():
return i
funcs.append(inner)
print([f() for f in funcs])
[2, 2, 2]
inner()는 i의 값을 캡처하지 않고 i 변수 자체를 참조i == 2 → 모든 함수에서 그 값 사용for i in range(3):
funcs.append(lambda i=i: i)
nonlocal vs global 차이x = 0
def outer():
x = 1
def inner():
nonlocal x
x += 1
return x
return inner()
print(outer())
print(x)
2
0
nonlocal x → 내부 inner()가 바깥 함수 outer()의 x에 접근x는 그대로 유지def inner():
global x
x += 1
→ 이제 x == 1이 됨
a = [1, 2, 3, 4]
b = [x for x in a if x % 2 == 0 else x * 10]
SyntaxError: invalid syntax
if-else는 앞에 써야 함b = [x * 10 if x % 2 else x for x in a] # 올바른 구문
✅ if-else는 표현식, for x in ... if ...는 필터
def add_value(key, val, container={}):
container[key] = val
return container
print(add_value('a', 1))
print(add_value('b', 2))
{'a': 1, 'b': 2}
container={}는 함수 정의 시 한 번만 생성됨def add_value(key, val, container=None):
if container is None:
container = {}
def recur(x, acc=[]):
if x == 0:
return acc
acc.append(x)
return recur(x-1, acc)
print(recur(3)) # [3,2,1]
print(recur(2)) # ???
[3, 2, 1]
[3, 2, 1, 2, 1]
acc=[]는 재귀로 누적되면서 계속 같은 리스트 사용def recur(x, acc=None):
if acc is None:
acc = []
| 유형 | 함정 요약 | 해결법 |
|---|---|---|
| closure + loop | 값이 아닌 참조 캡처 | lambda i=i:로 값 고정 |
| nonlocal vs global | 바깥 함수 vs 전역 | 차이 구별 필수 |
| comprehension | if-else 위치 오류 | x if cond else y 구조 기억 |
| dict 기본값 | dict 공유됨 | None + 조건 생성 |
| mutable + 재귀 | 누적됨 | 매 호출마다 새 객체 생성 |
좋아, 그럼 요청한 5번째 문제 — “Mutable Default + Recursion 오류 패턴” 을 완전 디버깅 모드로 한 줄씩 해부해줄게.
이건 정말 많은 초보자가 틀리는 대표적인 실수 유형이고, 파이썬 면접에서도 자주 출제되는 핵심 트랩이야.
def recur(x, acc=[]):
if x == 0:
return acc
acc.append(x)
return recur(x-1, acc)
print(recur(3)) # 예상 출력?
print(recur(2)) # 예상 출력?
[3, 2, 1]
[3, 2, 1, 2, 1]
def recur(x, acc=[])
acc는 기본 인자 값으로 빈 리스트 []를 가짐recur()를 여러 번 호출해도 같은 리스트 객체를 공유함recur(3)x = 3acc = [] ← 기본 인자 사용x = 3acc.append(3) → acc = [3]
→ recur(2, acc)
x = 2acc.append(2) → acc = [3, 2]
→ recur(1, acc)
x = 1acc.append(1) → acc = [3, 2, 1]
→ recur(0, acc)
x = 0return acc → [3, 2, 1]
✅ 이 값이 출력됨: print(recur(3)) → [3, 2, 1]
recur(2)이게 문제의 핵심이야.
[3, 2, 1] 상태임기본 인자는 함수 호출 간에 누적 유지됨
x = 2acc = [3, 2, 1] ← 이전 호출에서 누적된 리스트 그대로 재사용됨x = 2acc.append(2) → acc = [3, 2, 1, 2]
→ recur(1, acc)
x = 1acc.append(1) → acc = [3, 2, 1, 2, 1]
→ recur(0, acc)
x = 0return acc → [3, 2, 1, 2, 1]
✅ 이게 두 번째 출력
None + 내부에서 생성def recur(x, acc=None):
if acc is None:
acc = [] # 함수 안에서 매번 새로운 리스트 생성
if x == 0:
return acc
acc.append(x)
return recur(x-1, acc)
print(recur(3)) → [3, 2, 1]
print(recur(2)) → [2, 1] # 완전히 새로 시작!
| 호출 | 기본값 구조 (acc=[]) | 안전 구조 (acc=None) |
|---|---|---|
recur(3) | [3,2,1] | [3,2,1] |
recur(2) | [3,2,1,2,1] ← 오염됨 | [2,1] ← 정상 |
기본 인자는 언제 계산되나요?
→ 함수가 정의될 때 한 번만 계산됨
어떤 타입들이 위험한가요?
→ list, dict, set 등 mutable(변경 가능한 객체)
이런 구조는 왜 쓰는 건가요?
→ 속도를 위해 사용되지만 기본값 오염 버그 때문에 조심해야 함
[], {} 같은 mutable 객체 쓰지 마라None으로 받고 함수 안에서 초기화해라