class Test:
count = 0
def __init__(self):
self.items = []
Test.count += 1
def add(self, item):
self.items.append(item)
Test.count += 1
def show(self):
print("items:", self.items)
print("count:", Test.count)
a = Test()
b = Test()
a.add("apple")
b.add("banana")
a.show()
b.show()
items: ['apple']
count: 4
items: ['banana']
count: 4
items: ['apple']
count: 3
items: ['banana']
count: 3
items: ['apple']
count: 2
items: ['banana']
count: 2
items: ['apple', 'banana']
count: 4
items: ['apple', 'banana']
count: 4
count는 클래스 변수 → 모든 인스턴스에서 공유items는 인스턴스 변수 → 각 인스턴스마다 별도a = Test()__init__ 실행
self.items = [] → a의 인스턴스 변수 초기화Test.count += 1 → count = 1b = Test()또 __init__ 실행
b.items = []Test.count += 1 → count = 2a.add("apple")a.items.append("apple") → a.items = ['apple']Test.count += 1 → count = 3b.add("banana")b.items.append("banana") → b.items = ['banana']Test.count += 1 → count = 4a.show()items: ['apple']
count: 4
b.show()items: ['banana']
count: 4
| 변수 | 위치 | 공유 여부 | 값 변화 |
|---|---|---|---|
count | 클래스 변수 | 모든 인스턴스 공유 | 0 → 1 → 2 → 3 → 4 |
items | 인스턴스 변수 | 인스턴스별 개별 | a: ['apple'], b: ['banana'] |
좋아!
그럼 네가 요청한 Python의 실수하기 쉬운 4가지 주제에 대해 각각 실제 문제 + 디버깅 해설을 전부 해줄게.
(1번은 이미 위에서 해줬고, 이제 2번부터 4번까지 이어서 진행해.)
class A:
cnt = 0
def __init__(self):
A.cnt += 1
a1 = A()
a2 = A()
print(A.cnt)
👉 정답: 2
class A:
values = []
def __init__(self, x):
self.values.append(x)
a = A(1)
b = A(2)
a.values.append(3)
print(b.values)
👉 정답: [1, 2, 3]
🧠 이유: values는 클래스 변수이므로 a, b 둘 다 같은 리스트 공유
x = 10
def f():
x = x + 5
f()
① 정상 실행
② x는 15
③ global이 필요함
④ 오류 발생
👉 정답: ④ 오류 발생
🔍 해설: x = x + 5는 지역 변수 x를 새로 만들었다고 간주되는데, 오른쪽의 x가 초기화되지 않아서 UnboundLocalError
x = 7
def foo():
global x
x += 3
foo()
print(x)
👉 정답: 10
A. b = a[:]
B. b = copy.deepcopy(a)
C. b = copy.copy(a)
D. b = list(a)
👉 정답: A, C, D
deepcopy만 깊은 복사고 나머지는 얕은 복사야
import copy
a = [[0], [1]]
b = a[:]
a[0][0] = 99
print(b)
👉 정답: [[99], [1]]
👉 해설: 슬라이싱은 얕은 복사 → 내부 리스트는 참조됨
import copy
class C:
shared = []
def __init__(self, val):
self.personal = [val]
self.shared.append(val)
def modify(self):
new = copy.deepcopy(self.personal)
new[0] *= 10
return new
a = C(1)
b = C(2)
print(a.shared)
print(b.modify())
print(a.personal)
import copycopy 모듈을 가져온다.copy.deepcopy()를 쓰기 위해. 얕은 복사(shallow copy)와 달리, 리스트 안쪽까지 완전히 복사함.copy 모듈이 현재 파이썬에 등록됨.class C:C라는 사용자 정의 클래스를 만듦.shared = []C 자체가 가지는 변수로, 모든 인스턴스가 공유함.C.shared ──► []
def __init__(self, val):C()로 인스턴스를 만들 때 자동 호출됨.self.personal = [val]personal을 생성.self.shared.append(val)self.shared라고 썼지만, 이건 C.shared를 참조함 (인스턴스에 shared가 없으므로 클래스 변수로 감).C.shared에 val을 추가함.a = C(1)a.personal = [1]C.shared = [1]a.personal → [1]
C.shared → [1]
b = C(2)b.personal = [2]C.shared.append(2) → C.shared = [1, 2]b.personal → [2]
C.shared → [1, 2]
print(a.shared)a.shared는 인스턴스에 없으므로 클래스 변수 C.shared를 참조.C.shared = [1, 2]🔚 출력:
[1, 2]
print(b.modify())b.personal = [2]이므로, 그걸 복사해서 10배 만듦.copy.deepcopy(b.personal) → [2] 복사new[0] *= 10 → [20]🚨 중요:
new는 b.personal과 완전히 다른 리스트이므로, 원본은 그대로!🔚 출력:
[20]
print(a.personal)a.personal = [1]이므로 변하지 않았음. (복사한 건 b.personal)🔚 출력:
[1]
[1, 2]
[20]
[1]
C.shared ───────────────► [1, 2] ← 클래스 변수 (공용)
a.personal ─────────────► [1] ← 인스턴스 a만의 리스트
b.personal ─────────────► [2] ← 인스턴스 b만의 리스트
b.modify() → deepcopy([2]) = [20] ← 복사된 새로운 리스트 (원본과 무관)
| 항목 | 설명 |
|---|---|
C.shared | 클래스 전체에서 공유되는 리스트 |
self.personal | 인스턴스 고유 리스트 |
copy.deepcopy() | 리스트의 복사본을 만들어 원본 손상 없이 수정 가능 |
b.modify() | b.personal을 복사해서 계산, 원본은 그대로 둠 |
a.shared, b.shared | 전부 같은 리스트 C.shared를 가리킴 |
| 번호 | 주제 | 핵심 개념 |
|---|---|---|
| 1~2 | 클래스 변수 | 모든 인스턴스가 공유 |
| 3~4 | 전역 변수 | 지역 변수 오해 주의, global 필요 |
| 5~6 | 얕은 복사 | 내부 객체까지 공유됨 |
| 7~8 | 람다 캡처 | 참조 vs 값 캡처 구분 (lambda i=i) |
| 9 | 종합 문제 | 클래스 변수 + deepcopy + 인스턴스 변수 섞임 |
좋아! 이번엔 한 번 보면 대충 맞힐 수 있지만, 원리를 모르면 무조건 틀리는 수준의 어려운 문제를 준비했어.
이전 4가지 개념(클래스 변수, 전역 변수, 얕은/깊은 복사, 람다 캡처)을 섞거나 꼬아서 더 난이도 높은 실전 문제를 만들었어.
class Trick:
box = []
def __init__(self, val):
self.box = Trick.box
self.box.append(val)
a = Trick(1)
b = Trick(2)
b.box.append(3)
print(a.box)
print(b.box)
print(Trick.box)
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
Trick.box는 클래스 변수 → 모든 인스턴스에서 공유됨self.box = Trick.box → 사실상 self.box도 같은 리스트를 참조a.box, b.box, Trick.box 전부 동일한 리스트를 가리킴append()는 어디서 해도 전부 반영됨x = [10, 20]
def f():
global x
y = x
x = x[:]
x[0] += 100
y[1] += 100
f()
print(x)
[110, 20]
y = x → y와 x는 같은 리스트 [10, 20] 가리킴x = x[:] → 이제 x는 새로운 리스트 [10, 20] (얕은 복사!)x[0] += 100 → x는 [110, 20]y[1] += 100 → y는 원래의 [10, 20] → [10, 120]global x로 x는 새로운 리스트로 바뀜x = [110, 20] (y에서 바뀐 건 반영되지 않음) y[1] += 100 → y는 원래 x를 참조하고 있었으므로 x = x[:] 하기 전의 리스트를 참조class F:
funcs = []
def __init__(self, x):
F.funcs.append(lambda: x)
F(10)
F(20)
for f in F.funcs:
print(f())
10
20
lambda: x 는 __init__() 안에서 정의됨x는 지역 변수이자 정수 값이라 immutablelambda는 그 시점의 x를 값으로 캡처따라서 각각 10, 20이 그대로 유지됨
(만약 for문 안에서 lambda 만들었으면 20, 20 나왔을 수도 있음!)
다음 중 출력 결과가 [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
x=None 패턴으로 호출 시마다 새로운 리스트 생성됨