Phase 1 마무리를 향해 남은 두 주제를 진행: 튜플 활용법과 함수 심화(*args, **kwargs, 클로저). 문제 10개로 확인.
# 리스트 — 변경 가능 (mutable)
fruits = ["사과", "바나나", "딸기"]
fruits[0] = "포도" # OK
# 튜플 — 변경 불가 (immutable)
colors = ("빨강", "파랑", "초록")
colors[0] = "노랑" # TypeError!
Java에는 튜플이 없어서 생소할 수 있지만, Python에서는 엄청 자주 쓰인다. "이 데이터는 바뀌면 안 돼"라는 의도를 코드로 표현하는 것.
| 상황 | 예시 |
|---|---|
| 좌표처럼 묶인 데이터 | pos = (3, 5) |
| 함수에서 여러 값 반환 | return min_val, max_val |
| 딕셔너리 키로 사용 | grid[(x, y)] = "벽" |
| 언패킹 | name, age = ("홍길동", 25) |
특히 함수 반환값과 언패킹은 COS Pro에서 거의 매번 나온다.
# 생성
t1 = (1, 2, 3)
t2 = 1, 2, 3 # 괄호 없어도 튜플!
t3 = (42,) # 원소 1개일 때 반드시 콤마!
t4 = (42) # 이건 그냥 int 42
# 인덱싱/슬라이싱 — 리스트와 동일
t1[0] # 1
t1[1:] # (2, 3)
# 스왑 (Java에서는 temp 변수 필요!)
a, b = b, a
# 함수에서 여러 값 반환
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 7, 2]) # lo=1, hi=7
students = [("김철수", 85), ("이영희", 92), ("박민수", 78), ("정수진", 95)]
# 점수 기준 내림차순 정렬
students_sorted = sorted(students, key=lambda x: x[1], reverse=True)
# 1등 언패킹
top_name, top_score = students_sorted[0]
이 조합은 시험에서 정말 자주 나오니까 익숙해져야 한다.
디버깅 문제에서 나온 포인트. 첫 번째와 마지막 요소를 교환할 때:
# Java 스타일 — temp 필요
first = data[0]
data[0] = data[-1]
data[-1] = first
# Python — 동시 할당이라 한 줄이면 끝
data[0], data[-1] = data[-1], data[0]
Python은 우변을 먼저 전부 평가한 뒤 좌변에 할당하기 때문에 중간 변수가 필요 없다. 디버깅 문제에서 data[0], data[-1] = first, last처럼 같은 순서로 넣으면 아무것도 안 바뀌는 게 함정이었다.
# 키가 없으면 빈 리스트 만들고 → append
if grade not in groups:
groups[grade] = []
groups[grade].append(name)
# 한 줄 버전
groups.setdefault(grade, []).append(name)
수업 중 질문: "dict로 변환해서 keys()로 이름을 꺼내면 안 되나?"
→ 동작은 하지만 불필요한 변환이다. 이미 정렬된 튜플 리스트에서 리스트 컴프리헨션으로 바로 꺼내는 게 깔끔하다:[x[0] for x in sorted_list[:n]]
# Java에서는 오버로딩 2개 만들어야 했지만
# Python은 기본값 하나면 끝
def greet(name, greeting="안녕하세요"):
return f"{greeting}, {name}님!"
greet("홍길동") # "안녕하세요, 홍길동님!"
greet("홍길동", "반갑습니다") # "반갑습니다, 홍길동님!"
# *args — 가변 위치 인자 (튜플로 들어옴)
def add_all(*args):
return sum(args)
add_all(1, 2, 3, 4) # 10
# **kwargs — 가변 키워드 인자 (딕셔너리로 들어옴)
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="홍길동", age=25)
Java의 int... numbers (가변인자)와 비슷하지만, **kwargs는 Java에 없는 개념이다.
매개변수 순서: 일반 → 기본값 → *args → **kwargs
# class는 Python 예약어라서 직접 쓰면 SyntaxError!
make_tag("Image", "div", class="container") # 에러!
# 딕셔너리 언패킹으로 우회
make_tag("Image", "div", **{"class": "container"}) # OK!
딕셔너리 키는 문자열이라 예약어 충돌이 없다. **로 언패킹하면 키워드 인자로 넘어간다.
def create_counter(start=0):
count = start
def increment():
nonlocal count # "count는 바깥 함수 변수야"
count += 1
return count
return increment
| 키워드 | 의미 |
|---|---|
global | 모듈(파일) 최상위 변수 참조 |
nonlocal | 바깥 함수의 변수 참조 |
| 아무것도 안 쓰면 | 현재 함수의 지역변수 |
수업 중 질문: "함수 외부에 변수 안 두고 값을 저장해둘 수 있는 거야?"
→ Python의 클로저(closure)가 이걸 가능하게 한다. 안쪽 함수가 바깥 함수의 변수를 기억하고 있다가,nonlocal을 선언하면 수정까지 할 수 있다. Java에서는 클래스 필드에 저장해야 했던 걸, Python에서는 클로저로 간단하게 처리할 수 있다.
def apply_all(value, *functions):
result = value
for func in functions:
result = func(result)
return result
double = lambda x: x * 2
add_ten = lambda x: x + 10
apply_all(3, double, add_ten) # 3 → 6 → 16
Python에서는 함수도 변수처럼 넘기고 호출할 수 있다.
def filter_all(items, *conditions):
result = []
for item in items:
if all(condition(item) for condition in conditions):
result.append(item)
return result
is_positive = lambda x: x > 0
is_even = lambda x: x % 2 == 0
is_small = lambda x: x < 50
filter_all([-3, 4, 7, 12, -8, 50, 22], is_positive, is_even, is_small)
# → [4, 12, 22]
*conditions로 조건 함수를 여러 개 받고, all()로 모든 조건을 만족하는지 확인하는 패턴. 함수를 인자로 넘기는 것과 *args, all()이 합쳐진 종합 문제다.
리스트 컴프리헨션 버전:
def filter_all(items, *conditions):
return [item for item in items if all(c(item) for c in conditions)]
# 이렇게 쓰면 전부 같은 값이 나온다!
multipliers = []
for i in range(1, 4):
multipliers.append(lambda x: x * i) # i를 값이 아니라 변수 자체를 기억
# 전부 15 (i가 마지막에 3이 되어있으니까)
print(multipliers[0](5)) # 15!
# 함수로 감싸면 해결
def make_multiplier(n):
return lambda x: x * n # n에 그 시점의 값이 복사됨
lambda는 변수를 기억하지, 값을 기억하는 게 아니다. 루프 안에서 직접 쓸 때 주의!