연습14(b=a영향,한번씬 전체봐)

류한선·2025년 6월 30일

실기연습-2

목록 보기
62/95

좋아! 그럼 이번엔 방금 주제에 이어서 "리스트 트랩 + 헷갈리기 쉬운 삭제/슬라이싱/반복" 문제를 계속 내줄게.
특히 초보자가 많이 틀리는:

  • .del, .index(), .count()의 의외의 동작
  • 슬라이싱과 얕은 복사의 차이
  • for 루프 속에서 리스트 변경 시 건너뛰는 패턴
  • 리스트 안에서 리스트 있을 때의 참조 오류 (중첩 리스트)
  • 리스트 곱셈 연산자 *의 복사 트랩

📚 주제: 리스트 삭제/복사/슬라이싱/곱셈 연산 트랩 모음 (Python)(체크)


🔍 문제 1: del과 슬라이싱 범위 혼동

a = [0, 1, 2, 3, 4, 5]
del a[2:4]
print(a)

예상 결과:

  1. [0, 1, 4, 5]
  2. [0, 1, 2, 3, 4]
  3. [0, 1, 3, 4, 5]
  4. [0, 1, 5]

✅ 정답: 1번


🔍 해설:

a[2:4]2번 인덱스부터 4번 인덱스 '전까지' → 즉 [2, 3]

del로 그 부분을 삭제하면:

a = [0, 1, 2, 3, 4, 5]del a[2:4] → a = [0, 1, 4, 5]

🔍 문제 2: .index()의 첫 번째 항목 검색

a = [1, 3, 5, 3, 1]
print(a.index(3))

예상 출력:

  1. 1
  2. 2
  3. 3
  4. 4

✅ 정답: 1번


🔍 해설:

  • .index(value)는 리스트에서 첫 번째로 value와 일치하는 인덱스만 반환
  • a = [1, 3, 5, 3, 1]
  • a.index(3) → 3은 인덱스 1에 처음 등장

결과: 1


🔍 문제 3: 리스트 곱셈 연산자의 얕은 복사 문제 (체크)

a = [[0] * 3] * 2
a[0][0] = 1
print(a)

예상 결과:

  1. [[1, 0, 0], [0, 0, 0]]
  2. [[1, 0, 0], [1, 0, 0]]
  3. [[0, 0, 0], [1, 0, 0]]
  4. [[1, 0, 0], [0, 0, 1]]

✅ 정답: 2번


🔍 해설:

  • [[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

과는 다르다


🔍 문제 4: 리스트 슬라이싱 대참사 (체크)

a = [1, 2, 3, 4, 5]
b = a[2:]
b[0] = 99
print(a)

예상 결과:

  1. [1, 2, 3, 4, 5]
  2. [1, 2, 99, 4, 5]
  3. [1, 2, 3, 99, 5]
  4. [99, 2, 3, 4, 5]

✅ 정답: 1번


🔍 해설:

  • 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 (중첩 구조도 완전 복사)

🔍 문제 5: 중첩 리스트에서 for + remove 건너뛰기

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]
  2. [1, [3, 4], 5]
  3. [1, 2, [3, 4], 5]
  4. [1, [3, 4], 2]

✅ 정답: 1번


🔍 해설:

  • remove는 첫 번째 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]

✅ 정리 요약표

문제핵심 트랩 요소정답이유 요약
1del 슬라이싱 범위1번[2,3] 제거
2index는 첫 번째만 찾음1번인덱스 1의 3 반환
3리스트 곱셈의 얕은 복사2번내부 리스트 공유
4슬라이싱은 깊은 복사1번원본은 안 바뀜
5remove 중 반복 건너뛰기1번인덱스 밀려서 2 하나만 제거됨

👏 정확하게 짚었어!
문제 5번은 내가 처음에 정답을 잘못 제시했어.
네 말이 맞고, 정답은 **2번: [1, [3, 4], 5]**가 맞아.


📌 다시 보기: 문제 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]
❌ 이건 틀렸고


✅ 올바른 정답은: 2번: [1, [3, 4], 5]


🧠 그럼 이제 아주 디버깅 수준으로 흐름을 완전히 추적해볼게

초기 리스트:

a = [1, 2, [3, 4], 2, 5]

for x in a: 를 쓰면 → 리스트를 인덱스 순서대로 순회
근데, 중간에 remove()로 구조가 바뀌면,
반복은 그 다음 인덱스로 그냥 넘어감
→ 결과적으로 일부 요소가 건너뛰어짐


🧪 실행 추적표

루프x리스트 상태if x == 2remove()그 다음 순회 x (주의)
11[1, 2, [3, 4], 2, 5]없음x = 2
22[1, 2, [3, 4], 2, 5]remove(2) 실행리스트 = [1, [3, 4], 2, 5] → 2 삭제됨
3[3, 4][1, [3, 4], 2, 5]없음x = 2
42[1, [3, 4], 2, 5]remove(2) 실행리스트 = [1, [3, 4], 5]
55[1, [3, 4], 5]없음종료

🎯 핵심 요약

  • a.remove(x)는 해당 값을 하나만 지움 (앞에서부터)
  • 리스트 구조가 바뀌어도 for x in a남아있는 요소들 순서대로 계속 순회
  • 결국 두 개의 2가 모두 삭제
  • [3, 4]2가 아니므로 영향 없음

✅ 최종 출력

print(a)[1, [3, 4], 5]

정답: 2번


🙇 내 오류 정리

잘못 제시한 정답이유
1번 [1, [3, 4], 2, 5]첫 번째 2만 삭제되고 두 번째 2가 건너뛰어진다고 잘못 해석했음
실제 동작for x in a:는 구조가 바뀌더라도 남은 요소 순서대로 이어감

너처럼 직접 확인하고 물어보는 자세는 진짜 중요해.
시험에서 “이거 remove인데 왜 다 없어졌지?” 또는 “왜 얘만 남았지?” 같은 일이 정말 자주 나와.


훌륭해. 아래는 네가 요청한 4가지 주제에 대해 실전처럼 나올 수 있는 낚시 문제 스타일 + 정답 + 완전 해부 설명이야.
이런 건 정보처리기사 실기에서 코드 해석형으로 충분히 나올 수 있고, 현업에서도 실수 잦은 함정이야.


🧠 1. 딕셔너리 .get()key shadowing 트랩 (체크)

🔍 문제

d = {"x": 10, "y": 20}
val = d.get("z", d["x"] + d["y"])
print(val)

출력 결과는?

  1. 30
  2. None
  3. KeyError
  4. "x"

✅ 정답: 1번: 30


🧠 해설

  • d.get("z", d["x"] + d["y"])"z"라는 키가 없으면 기본값으로 d["x"] + d["y"] 를 사용하라는 의미
  • 하지만 .get() 함수는 기본값 파라미터라도 무조건 먼저 계산됨!
  • 즉, "z"가 없더라도 이미 d["x"] + d["y"]10 + 20 = 30 이 계산된 상태

주의: 기본값도 평가되고 실행됨. 존재 여부와 관계없이!


⚠️ key shadowing (변수명이 dict key와 겹침)

x = {"x": 5}
x["x"] = x  # ❗ 변수 x 자체를 key "x"의 값으로 넣음
print(x["x"]["x"]["x"]["x"]["x"])

✅ 정답: 무한 중첩 가능

(딕셔너리 안에 자기 자신을 계속 넣는 구조 → 재귀형 구조됨)

이런 거 실기에서 내면 문제에 "다섯 번째 접근까지 출력될 수 있는가?" 같은 식으로 꼬아서 냄.


🧠 2. set의 .remove() vs .discard() 트랩

🔍 문제

s = {1, 2, 3}
s.discard(4)
s.remove(4)

출력 결과는?

  1. 아무 일 없음
  2. KeyError
  3. None
  4. False

✅ 정답: 2번: KeyError


🧠 해설

  • discard(4) → 없어도 조용히 넘어감 (안전)
  • remove(4) → 없으면 KeyError 발생
s = {1, 2, 3}
s.discard(4)  # 아무 일 없음
s.remove(4)   # ❌ KeyError: 4 not in set

✅ 실기에서는 .remove()만 있는 줄 알고 discard() 대신 쓰는 실수 유도


🧠 3. 함수 내부 변수 스코프 (전역/지역 충돌)

🔍 문제

count = 5

def func():
    count += 1
    print(count)

func()

예상 출력:

  1. 6
  2. 5
  3. UnboundLocalError
  4. NameError

✅ 정답: 3번: UnboundLocalError


🧠 해설

  • count += 1은 사실상 → count = count + 1이라는 의미
  • 그런데 함수 내에서 count를 먼저 읽으려고 시도하니까 파이썬은 "지역 변수로 해석"
  • 하지만 지역 변수로 선언도 안 됐는데 읽으려고 해서 오류
UnboundLocalError: local variable 'count' referenced before assignment

✅ 해결법: global count 선언하거나, 지역 변수명 다르게 써야 함


🧠 4. 함수 기본 인자 (mutable default 트랩)

🔍 문제

def append_to(val, lst=[]):
    lst.append(val)
    return lst

print(append_to(1))
print(append_to(2))
print(append_to(3))

출력 결과는?

  1. [1], [2], [3]
  2. [1], [1, 2], [1, 2, 3]
  3. [1], [1], [1]
  4. None, None, None

✅ 정답: 2번


🧠 해설

  • 기본 인자 lst=[]는 한 번만 초기화됨!
  • 함수 호출할 때마다 새 리스트가 생기는 게 아니라,
  • 처음 만든 리스트를 계속 공유하게 됨
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: 선처리
setremove vs discard없는 값 제거 시 에러discard는 안전
변수 스코프전역 변수 수정지역으로 인식되며 충돌global, nonlocal 명시
기본 인자mutable default값 공유됨None을 쓰고 내부에서 새로 만듦

좋아! 지금 정확하게 짚어야 할 건 Python의 집합(set) 메서드인 .discard()기능과 동작 방식, 그리고 .remove()와의 차이야.


.discard(x)는 뭐 하는 역할이야?

  • **집합(set)**에서 지정한 원소 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() 추천예외 처리 따로 해야 함

0개의 댓글