맞아! 아까 우리가 다룬 건 1번부터 15번까지, 기본기 + 실전에서 자주 쓰이지만 처음 보면 헷갈리는 문법/구조들이었어.
하지만 그 외에도 정보처리기사 실기 수준 또는 조금만 난이도 올라가면 반드시 알아야 하는 파이썬 핵심 문법이 꽤 더 있어.
| 번호 | 주제 | 핵심 포인트 |
|---|---|---|
| 1~2 | 리스트 컴프리헨션 | 간결한 반복문 |
| 3~5 | enumerate, zip 등 | 반복문과 함께 사용 |
| 6~8 | 람다, map, filter | 함수형 문법 |
| 9 | ** (제곱, 언팩) | 두 가지 쓰임 |
| 10 | *args, **kwargs | 가변 인자 |
| 11 | try-except | 예외 처리 |
| 12 | heapq, itertools, collections | 자주 쓰이는 모듈 |
| 13 | is vs == | 비교 트랩 |
| 14 | in 연산자 | 동작 방식 자료형별 다름 |
| 15 | list vs set vs dict | 내부 구조 비교 |
➡ 아래는 정보처리기사 실기 + 알고리즘 문제 + 파이썬 중급 레벨에서
거의 필수로 자주 튀어나오는 핵심 문법들이야.
우리가 16번 이후로 이어가면 좋을 주제들이기도 해.
| 번호 | 주제 | 왜 중요한가? |
|---|---|---|
| 16 | sorted() vs sort() | 리턴 차이로 자주 실수함 |
| 17 | copy() vs deepcopy() | 얕은 복사 트랩 |
| 18 | 리스트 슬라이싱 [:] | 참조 vs 복사 헷갈림 |
| 19 | defaultdict, Counter | 카운팅 문제의 왕 |
| 20 | reversed(), [::-1] | 리스트 뒤집기 차이 |
| 21 | 문자열 처리 함수 | strip(), split(), join() |
| 22 | yield와 제너레이터 | 메모리 효율형 반복자 |
| 23 | any(), all() | 조건 검사에 깔끔하게 사용 |
| 24 | with open() | 파일 입출력은 실기 기본 |
| 25 | 클래스와 __init__ | 구조화된 데이터 처리용 |
| 주제 | 설명 |
|---|---|
| 파이썬 자료형 별 시간복잡도 | set, dict, list 각각 어떤 연산이 빠른지 |
| 순열/조합/백트래킹 직접 구현 | itertools 없이도 구현할 줄 알아야 함 |
입출력 속도 처리 (sys.stdin.read) | 대량 입력에서 필수 |
| 객체의 참조, 얕은 복사 vs 깊은 복사 | 실기에서 리스트 변경 트랩 자주 나옴 |
좋아! 이제 16번부터 20번까지, 실전에서 자주 나오는 파이썬 문법들을 1줄씩 완전히 해부해서
왜 헷갈리는지, 어떻게 써야 맞는지, 실수하면 어디서 터지는지까지 아주 천천히 설명해줄게.
sort() vs sorted() 차이 (리스트 정렬 트랩)sort() — 원본 리스트를 정렬 (제자리 변경)a = [3, 1, 2]
a.sort()
print(a) # [1, 2, 3]
📌 a.sort()는 원본 리스트 자체를 바꿔버림, 리턴값은 None
sorted() — 새로운 리스트로 정렬a = [3, 1, 2]
b = sorted(a)
print(a) # [3, 1, 2]
print(b) # [1, 2, 3]
📌 sorted(a)는 새로운 정렬된 리스트를 반환함. 원본은 그대로!
a = [3, 1, 2]
b = a.sort()
print(b) # ❌ None
👉 b = a.sort()는 항상 None이니까 주의!
→ 정렬된 리스트가 필요하면 sorted(a) 써야 함
| 함수 | 원본 변경? | 리턴값 | 사용 위치 |
|---|---|---|---|
sort() | ✅ 바뀜 | None | 리스트 전용 |
sorted() | ❌ 안 바뀜 | 새 리스트 | 모든 반복형 자료 가능 |
copy() vs deepcopy() (복사 트랩)copy())import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
b[0][0] = 99
print(a) # [[99, 2], [3, 4]]
➡ 바깥 리스트만 복사하고, 내부 리스트는 여전히 같은 객체를 참조함
deepcopy())import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0][0] = 99
print(a) # [[1, 2], [3, 4]]
➡ 내부까지 완전히 새로운 복사본을 만들어줌
| 복사 방식 | 내부 리스트까지 분리됨? | 사용 상황 |
|---|---|---|
copy() | ❌ 참조 유지됨 | 1단계 리스트 |
deepcopy() | ✅ 완전 복제 | 중첩 리스트/객체 |
[:] (복사 vs 참조 혼동)a = [1, 2, 3]
b = a[:]
b[0] = 99
print(a) # [1, 2, 3]
print(b) # [99, 2, 3]
➡ [:]는 새로운 리스트를 만들지만, 내부 값이 객체면 여전히 얕은 복사
a = [[0] * 3] * 3
a[0][0] = 1
print(a)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]] ← ❌ 모든 행이 연결됨
✅ 이건 3개의 리스트가 같은 리스트를 참조함
올바른 방법은:
a = [[0]*3 for _ in range(3)]
defaultdict, Counter (카운팅과 기본값)defaultdictfrom collections import defaultdict
d = defaultdict(int)
d['a'] += 1
print(d['a']) # 1
➡ 기본값이 0으로 자동 설정됨
→ 실기에서 "키가 없으면 에러" 방지용
Counterfrom collections import Counter
cnt = Counter(['a', 'b', 'a', 'c', 'a'])
print(cnt['a']) # 3
➡ 요소별 등장 횟수를 자동으로 세어줌
→ 리스트/문자열에서 빈도 계산할 때 최고
reversed(), [::-1] (역순 처리)reversed() 함수a = [1, 2, 3]
for x in reversed(a):
print(x) # 3 2 1
➡ 메모리 낭비 없이 순회만 역순
[::-1] 슬라이싱a = [1, 2, 3]
b = a[::-1]
print(b) # [3, 2, 1]
➡ 새로운 리스트를 만들어 역순으로 리턴
→ 순회보다 값 자체가 필요한 경우 사용
| 방식 | 복사 여부 | 속도/메모리 |
|---|---|---|
reversed() | ❌ 복사 안 함 | 메모리 효율적 |
[::-1] | ✅ 복사함 | 리스트 새로 만듦 |
| 번호 | 주제 | 핵심 요점 | 실전 트랩 |
|---|---|---|---|
| 16 | sort() vs sorted() | 정렬은 sorted()가 리턴 있음 | b = a.sort()는 None |
| 17 | copy() vs deepcopy() | 중첩 구조 복사는 deepcopy() | copy()는 내부 참조 유지 |
| 18 | 슬라이싱 [:] | 복사되지만 얕은 복사 | 2차원 리스트 만들 때 터짐 |
| 19 | defaultdict, Counter | 기본값 및 카운팅 자동화 | dict['x'] += 1 에러 해결 |
| 20 | [::-1], reversed() | 역순 처리 방식 | reversed()는 복사 안 함 |
좋아, 이제 21번부터 25번까지 이어서 설명해줄게.
이 단계의 문법들은 실제 정보처리기사 실기에서 거의 반드시 등장하는 기본기이자,
파이썬을 능숙하게 쓰는 사람과 헷갈리는 초보자를 가르는 핵심 문법들이야.
strip(), rstrip(), lstrip()s = " hello \n"
print(s.strip()) # 'hello'
print(s.lstrip()) # 'hello \n'
print(s.rstrip()) # ' hello'
strip()은 양쪽, lstrip()은 왼쪽, rstrip()은 오른쪽만split()s = "a b c"
print(s.split()) # ['a', 'b', 'c']
print(s.split(" ")) # ['a', 'b', 'c']
'a,b,c'.split(',') → ['a', 'b', 'c']join()lst = ['a', 'b', 'c']
print(' '.join(lst)) # 'a b c'
print(','.join(lst)) # 'a,b,c'
input = "1,2,3"
numbers = input.split()
print(numbers) # ❌ ['1,2,3']
→ split(',')로 써야 함
yield와 제너레이터def normal():
return [1, 2, 3]
def gen():
yield 1
yield 2
yield 3
print(normal()) # [1, 2, 3]
print(list(gen())) # [1, 2, 3]
yield는 값을 하나씩 반환하고 함수의 상태를 유지for 루프로 순회 가능)def count_up_to(n):
for i in range(1, n + 1):
yield i
for x in count_up_to(3):
print(x)
출력:
1
2
3
| 항목 | 리스트 | 제너레이터 (yield) |
|---|---|---|
| 메모리 사용 | 크다 | 작다 |
| 속도 | 빠름 | 느릴 수 있음 |
| 사용 시점 | 미리 다 계산 | 하나씩 계산 |
any() vs all() (조건 검사 축약)any() – 하나라도 True면 Truea = [0, 0, 1]
print(any(a)) # True
all() – 모두 True여야 Truea = [1, 1, 1]
print(all(a)) # True
b = [1, 0, 1]
print(all(b)) # False
lst = ['abc', 'bcd', 'cde']
print(any(word.startswith('a') for word in lst)) # True
print(all(len(word) == 3 for word in lst)) # True
→ 리스트 조건 검사 시 매우 깔끔하게 사용 가능
with open() (파일 입출력의 정석)with open('file.txt', 'r') as f:
data = f.read()
print(data)
f로 다룬다with open('file.txt', 'w') as f:
f.write('Hello\n')
f = open('file.txt', 'r')
data = f.read()
# 닫는 걸 까먹음 → 메모리/리소스 누수
✅ 항상 with를 쓰는 게 안전함
__init__ (초기화 함수)class Person:
def __init__(self, name):
self.name = name
p = Person("Alice")
print(p.name) # Alice
__init__은 생성자 (객체가 생성될 때 자동 실행)self는 인스턴스 자신을 가리킴class A:
def __init__(self, val=[]): # ❌ 위험한 기본값
self.val = val
→ 이건 모든 인스턴스가 같은 리스트 공유함
✅ 항상 None으로 초기화 후 내부에서 새 리스트 만들 것
def __init__(self, val=None):
self.val = val if val is not None else []
| 번호 | 주제 | 핵심 포인트 | 실전 용도 |
|---|---|---|---|
| 21 | 문자열 함수 | strip, split, join | 입력 전처리 |
| 22 | yield | 제너레이터 함수 | 메모리 아낌 |
| 23 | any, all | 조건검사 축약 | 리스트 검사 |
| 24 | with open() | 파일 자동 닫힘 | 입출력 안전하게 |
| 25 | __init__ | 클래스 초기화 | 객체 설계 핵심 |