좋아! 그럼 이번엔 방금 주제에 이어서 "리스트 트랩 + 헷갈리기 쉬운 삭제/슬라이싱/반복" 문제를 계속 내줄게.
특히 초보자가 많이 틀리는:
.del, .index(), .count()의 의외의 동작for 루프 속에서 리스트 변경 시 건너뛰는 패턴*의 복사 트랩del과 슬라이싱 범위 혼동a = [0, 1, 2, 3, 4, 5]
del a[2:4]
print(a)
[0, 1, 4, 5][0, 1, 2, 3, 4][0, 1, 3, 4, 5][0, 1, 5]a[2:4]는 2번 인덱스부터 4번 인덱스 '전까지' → 즉 [2, 3]
→ del로 그 부분을 삭제하면:
a = [0, 1, 2, 3, 4, 5]
→ del a[2:4] → a = [0, 1, 4, 5]
.index()의 첫 번째 항목 검색a = [1, 3, 5, 3, 1]
print(a.index(3))
1234.index(value)는 리스트에서 첫 번째로 value와 일치하는 인덱스만 반환a = [1, 3, 5, 3, 1]a.index(3) → 3은 인덱스 1에 처음 등장결과: 1
a = [[0] * 3] * 2
a[0][0] = 1
print(a)
[[1, 0, 0], [0, 0, 0]][[1, 0, 0], [1, 0, 0]][[0, 0, 0], [1, 0, 0]][[1, 0, 0], [0, 0, 1]][[0]*3]*2 → 내부 리스트 두 개를 만들지만, 같은 리스트 객체를 2번 참조함a[0], a[1]은 동일한 객체임a[0][0] = 1 # 결국 a[1][0]도 같이 바뀜
→ 결과: [[1, 0, 0], [1, 0, 0]]
앞의 연습11 의
x = [[0]] * 3
과는 다르다
a = [1, 2, 3, 4, 5]
b = a[2:]
b[0] = 99
print(a)
[1, 2, 3, 4, 5][1, 2, 99, 4, 5][1, 2, 3, 99, 5][99, 2, 3, 4, 5]b = a[2:]는 슬라이싱을 통해 복사본을 만듦b[0] = 99는 b 내부만 바뀜 → a는 영향 없음a = [1, 2, 3, 4, 5]
b = [3, 4, 5] # a[2:] 복사됨
b[0] = 99 → b = [99, 4, 5]
a는 그대로 → [1, 2, 3, 4, 5]
복사 방식: 이건 얕은 복사(shallow copy)가 아니라, 부분 복사한 새 리스트입니다.
하지만 내부에 중첩된 객체가 있다면 얕은 복사로 작용하게 됩니다.
a → [1, 2, 3, 4, 5]
↑
슬라이싱 시작 (index 2)
b → [3, 4, 5] # 이건 새 리스트! a와 연결되지 않음.
| 상황 | 복사 방식 | 영향을 주는가? |
|---|---|---|
b = a | 참조 (같은 객체) | O (둘 다 영향받음) |
b = a[:] | 얕은 복사 | X (1차원에서는 영향 없음) |
b = a[2:] | 슬라이싱 → 새로운 리스트 | X (새 객체) |
copy.copy(a) | 얕은 복사 | X (1차원에서는 영향 없음) |
copy.deepcopy(a) | 깊은 복사 | X (중첩 구조도 완전 복사) |
a = [1, 2, [3, 4], 2, 5]
for x in a:
if x == 2:
a.remove(x)
print(a)
[1, [3, 4], 2, 5][1, [3, 4], 5][1, 2, [3, 4], 5][1, [3, 4], 2]2만 제거되면, 다음 루프 때 인덱스 밀려서 두 번째 2는 건너뛰게 됨a = [1, 2, [3, 4], 2, 5]
1. x = 1 → 패스
2. x = 2 → remove(2) → a = [1, [3, 4], 2, 5]
3. x = [3, 4] → 패스
4. x = 2 (건너뜀❗) → 반복 종료
결과: [1, [3, 4], 2, 5]
| 문제 | 핵심 트랩 요소 | 정답 | 이유 요약 |
|---|---|---|---|
| 1 | del 슬라이싱 범위 | 1번 | [2,3] 제거 |
| 2 | index는 첫 번째만 찾음 | 1번 | 인덱스 1의 3 반환 |
| 3 | 리스트 곱셈의 얕은 복사 | 2번 | 내부 리스트 공유 |
| 4 | 슬라이싱은 깊은 복사 | 1번 | 원본은 안 바뀜 |
| 5 | remove 중 반복 건너뛰기 | 1번 | 인덱스 밀려서 2 하나만 제거됨 |
👏 정확하게 짚었어!
문제 5번은 내가 처음에 정답을 잘못 제시했어.
네 말이 맞고, 정답은 **2번: [1, [3, 4], 5]**가 맞아.
a = [1, 2, [3, 4], 2, 5]
for x in a:
if x == 2:
a.remove(x)
print(a)
1번:
[1, [3, 4], 2, 5]
❌ 이건 틀렸고
[1, [3, 4], 5]초기 리스트:
a = [1, 2, [3, 4], 2, 5]
for x in a: 를 쓰면 → 리스트를 인덱스 순서대로 순회함
근데, 중간에 remove()로 구조가 바뀌면,
반복은 그 다음 인덱스로 그냥 넘어감
→ 결과적으로 일부 요소가 건너뛰어짐
| 루프 | x | 리스트 상태 | if x == 2 | remove() | 그 다음 순회 x (주의) |
|---|---|---|---|---|---|
| 1 | 1 | [1, 2, [3, 4], 2, 5] | ❌ | 없음 | x = 2 |
| 2 | 2 | [1, 2, [3, 4], 2, 5] | ✅ | remove(2) 실행 | 리스트 = [1, [3, 4], 2, 5] → 2 삭제됨 |
| 3 | [3, 4] | [1, [3, 4], 2, 5] | ❌ | 없음 | x = 2 |
| 4 | 2 | [1, [3, 4], 2, 5] | ✅ | remove(2) 실행 | 리스트 = [1, [3, 4], 5] |
| 5 | 5 | [1, [3, 4], 5] | ❌ | 없음 | 종료 |
a.remove(x)는 해당 값을 하나만 지움 (앞에서부터)for x in a는 남아있는 요소들 순서대로 계속 순회함[3, 4]는 2가 아니므로 영향 없음print(a) → [1, [3, 4], 5]
정답: 2번
| 잘못 제시한 정답 | 이유 |
|---|---|
1번 [1, [3, 4], 2, 5] | 첫 번째 2만 삭제되고 두 번째 2가 건너뛰어진다고 잘못 해석했음 |
| 실제 동작 | for x in a:는 구조가 바뀌더라도 남은 요소 순서대로 이어감 |
너처럼 직접 확인하고 물어보는 자세는 진짜 중요해.
시험에서 “이거 remove인데 왜 다 없어졌지?” 또는 “왜 얘만 남았지?” 같은 일이 정말 자주 나와.
훌륭해. 아래는 네가 요청한 4가지 주제에 대해 실전처럼 나올 수 있는 낚시 문제 스타일 + 정답 + 완전 해부 설명이야.
이런 건 정보처리기사 실기에서 코드 해석형으로 충분히 나올 수 있고, 현업에서도 실수 잦은 함정이야.
.get()과 key shadowing 트랩 (체크)d = {"x": 10, "y": 20}
val = d.get("z", d["x"] + d["y"])
print(val)
30NoneKeyError"x"d.get("z", d["x"] + d["y"]) 는 "z"라는 키가 없으면 기본값으로 d["x"] + d["y"] 를 사용하라는 의미.get() 함수는 기본값 파라미터라도 무조건 먼저 계산됨!"z"가 없더라도 이미 d["x"] + d["y"] → 10 + 20 = 30 이 계산된 상태✅ 주의: 기본값도 평가되고 실행됨. 존재 여부와 관계없이!
x = {"x": 5}
x["x"] = x # ❗ 변수 x 자체를 key "x"의 값으로 넣음
print(x["x"]["x"]["x"]["x"]["x"])
(딕셔너리 안에 자기 자신을 계속 넣는 구조 → 재귀형 구조됨)
이런 거 실기에서 내면 문제에 "다섯 번째 접근까지 출력될 수 있는가?" 같은 식으로 꼬아서 냄.
.remove() vs .discard() 트랩s = {1, 2, 3}
s.discard(4)
s.remove(4)
KeyErrorNoneFalsediscard(4) → 없어도 조용히 넘어감 (안전)remove(4) → 없으면 KeyError 발생s = {1, 2, 3}
s.discard(4) # 아무 일 없음
s.remove(4) # ❌ KeyError: 4 not in set
✅ 실기에서는
.remove()만 있는 줄 알고discard()대신 쓰는 실수 유도
count = 5
def func():
count += 1
print(count)
func()
65UnboundLocalErrorNameErrorcount += 1은 사실상 → count = count + 1이라는 의미UnboundLocalError: local variable 'count' referenced before assignment
✅ 해결법:
global count선언하거나, 지역 변수명 다르게 써야 함
def append_to(val, lst=[]):
lst.append(val)
return lst
print(append_to(1))
print(append_to(2))
print(append_to(3))
[1], [2], [3][1], [1, 2], [1, 2, 3][1], [1], [1]None, None, Nonelst=[]는 한 번만 초기화됨!append_to(1) → [1] # 초기화
append_to(2) → [1, 2] # 같은 리스트에 추가됨
append_to(3) → [1, 2, 3]
✅ 안전하게 쓰려면:
def append_to(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
| 주제 | 트랩 유형 | 핵심 실수 | 안전 대처 |
|---|---|---|---|
| dict.get | 기본값 파라미터 평가됨 | 키 없어도 계산됨 | 계산 비용 클 땐 if k in d: 선처리 |
| set | remove vs discard | 없는 값 제거 시 에러 | discard는 안전 |
| 변수 스코프 | 전역 변수 수정 | 지역으로 인식되며 충돌 | global, nonlocal 명시 |
| 기본 인자 | mutable default | 값 공유됨 | None을 쓰고 내부에서 새로 만듦 |
좋아! 지금 정확하게 짚어야 할 건 Python의 집합(set) 메서드인 .discard()의 기능과 동작 방식, 그리고 .remove()와의 차이야.
.discard(x)는 뭐 하는 역할이야?x가 집합 안에 존재하면 제거,x가 집합에 없어도 아무 에러 없이 조용히 넘어감.s = {1, 2, 3}
s.discard(2) # 2가 있으니까 제거됨 → s = {1, 3}
s.discard(5) # 5는 없지만 에러 없음 → 그대로 s = {1, 3}
.remove()와 비교| 메서드 | 존재할 때 동작 | 없을 때 동작 |
|---|---|---|
s.discard(x) | x 제거 | ❗ 에러 없이 그냥 무시 |
s.remove(x) | x 제거 | ❗ KeyError 발생 |
s = {1, 2, 3}
s.discard(4) # OK. 아무 일 없음
print(s) # {1, 2, 3}
s.remove(4) # ❌ KeyError: 4 not in set
discard()를 써야 해?for item in [2, 4, 6]:
my_set.discard(item) # 안전하게 처리
| 상황 | .discard(x) | .remove(x) |
|---|---|---|
| 요소 x가 있는 경우 | 제거함 | 제거함 |
| 요소 x가 없는 경우 | 그냥 지나감 (무시) | ❗ KeyError 발생 |
| 안전하게 사용하고 싶을 때 | discard() 추천 | 예외 처리 따로 해야 함 |