연습11(중간 체크말고도 그냥 다 봐)

류한선·2025년 6월 29일

실기연습-2

목록 보기
59/95

좋아!
그럼 아까 마지막에 낸 보너스 퀴즈 문제를 한 줄 한 줄 완전 디버깅 모드로 분석해줄게.
이건 정말 많은 사람이 착각하거나 헷갈리는 문제라 시험에서도 자주 응용되는 유형이야.


✅ 보너스 퀴즈 다시 보기


❓ 문제:
다음 중 출력 결과가 [1, 2, 3]이 아닌 것은?


A

x = []

def add():
    x.append(1)

add()
x.append(2)
add()
print(x)

B

def add(x=[]):
    x.append(1)
    return x

print(add())
print(add())
print(add())

C

def add(x=None):
    if x is None:
        x = []
    x.append(1)
    return x

print(add())
print(add())
print(add())

✅ 정답: C


이제 각 선택지를 한 줄씩 디버깅처럼 해부하자.


🔸 A 선택지 디버깅

x = []  # 전역 리스트 선언

def add():
    x.append(1)
  • 함수 안에서 전역 변수 x직접 수정만 하므로 global 없이도 정상 작동
  • 리스트는 mutable 객체이기 때문에 .append()는 OK

add()  → 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]이 아닌 것"을 묻는 것이므로
이건 정답 후보다.


🔸 B 선택지 디버깅

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] 아님
→ 이것도 정답 후보


🔸 C 선택지 디버깅

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] 나오는 코드만 골라내는 것이면…


재정의 (문제 의도)

만약 아래처럼 각 코드가 마지막에 한 줄만 출력한다면?


B를 이렇게 바꾸면?

x = add()
x = add()
x = add()
print(x)

x = [1, 1, 1][1, 2, 3] 아님 → 정답 후보


C를 이렇게 바꾸면?

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=Nonex=[]매 호출마다 새 리스트 생성됨 → 안전

좋아! 이번엔 아까 배운 주제보다 더 함정을 잘 숨긴 고급 버전 문제들을 줄게.
특히 dict, set, defaultdict, list comprehension, closure + loop`, lambda의 변수 캡처 문제를 섞어서,
기본기가 없으면 헷갈릴 수밖에 없는 수준으로 구성할게.


✅ 고급 파이썬 함정 문제 세트 (Comprehension / Closure / Collection)


🔸 문제 1: list comprehension과 변수 참조 (체크)

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: ii 값을 캡처한 것이 아니라 i라는 변수 자체를 참조
  • range(3) 루프가 끝나고 나면 i == 2
  • 따라서 모든 lambda에서 i == 2

✅ 정답: B. [2, 2, 2]


🔸 문제 2: dict의 key 동적 변경 + 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]}


🔸 문제 3: defaultdict와 lambda 기본값 (체크)

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]}


🔸 문제 4: set에서 리스트 사용

s = set()
s.add([1, 2])
print(s)

선택지:

A. set([1, 2])
B. [[1, 2]]
C. 오류 발생
D. 빈 세트


🔍 해설:

  • set의 원소는 hashable(변경 불가능) 해야 함
  • list는 mutable → hash 불가능 → TypeError

✅ 정답: C. 오류 발생


🔸 문제 5: comprehension과 변수 누적 공유

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)

✅ 2. 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는 그대로 유지

✅ global을 썼다면?

def inner():
    global x
    x += 1

→ 이제 x == 1이 됨


✅ 3. List comprehension 조건문 꼬기

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 ...는 필터


✅ 4. 함수 기본값에 dict 사용 실수

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={}는 함수 정의 시 한 번만 생성됨
  • 따라서 여러 번 호출해도 같은 dict 공유

✅ 해결법

def add_value(key, val, container=None):
    if container is None:
        container = {}

✅ 5. Mutable Default + Recursion 오류 패턴

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 전역차이 구별 필수
comprehensionif-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 = 3
  • acc = [] ← 기본 인자 사용

🔄 첫 번째 재귀: x = 3

acc.append(3)  → acc = [3]
→ recur(2, acc)

🔄 두 번째 재귀: x = 2

acc.append(2)  → acc = [3, 2]
→ recur(1, acc)

🔄 세 번째 재귀: x = 1

acc.append(1)  → acc = [3, 2, 1]
→ recur(0, acc)

🔚 네 번째 재귀: x = 0

return acc → [3, 2, 1]

✅ 이 값이 출력됨: print(recur(3)) → [3, 2, 1]


🧩 두 번째 호출: recur(2)

이게 문제의 핵심이야.


⚠️ 이미 acc는 [3, 2, 1] 상태임

기본 인자는 함수 호출 간에 누적 유지됨


🎯 초기 상태

  • x = 2
  • acc = [3, 2, 1] ← 이전 호출에서 누적된 리스트 그대로 재사용됨

🔄 첫 번째 재귀: x = 2

acc.append(2)  → acc = [3, 2, 1, 2]
→ recur(1, acc)

🔄 두 번째 재귀: x = 1

acc.append(1)  → acc = [3, 2, 1, 2, 1]
→ recur(0, acc)

🔚 세 번째 재귀: x = 0

return 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] ← 정상

💡 이 문제에서 시험이나 실무에서 자주 나오는 질문

  1. 기본 인자는 언제 계산되나요?
    → 함수가 정의될 때 한 번만 계산됨

  2. 어떤 타입들이 위험한가요?
    list, dict, setmutable(변경 가능한 객체)

  3. 이런 구조는 왜 쓰는 건가요?
    → 속도를 위해 사용되지만 기본값 오염 버그 때문에 조심해야 함


🔚 결론 정리

  • 함수 기본값에 [], {} 같은 mutable 객체 쓰지 마라
  • 대신 None으로 받고 함수 안에서 초기화해라
  • 재귀나 상태 누적 함수에서 기본값은 매우 치명적이다

0개의 댓글