모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

18강 / 전체 32강

매개변수 완전 분석 — 위치, 키워드, 가변, 기본값

9분 읽기 조회 11

파이썬 함수의 모든 매개변수 종류와 가변 객체 기본값 함정을 완전히 이해한다.

🎯 이 강의에서 배우는 것

파이썬의 매개변수 시스템은 매우 정교합니다. 위치·키워드·가변·기본값·위치 전용·키워드 전용 인수까지 6가지 종류가 있습니다. 그 중 가변 객체 기본값의 함정은 파이썬 개발자 모두가 한 번은 맞닥뜨리는 유명한 버그입니다. 이 강의에서 모든 패턴을 완벽하게 이해합니다.

📌 위치 인자와 키워드 인자

def describe_person(name, age, city):
    print(f"{name}님은 {city}에 사는 {age}세입니다.")

# 위치 인자: 순서 중요
describe_person("홍길동", 30, "서울")

# 키워드 인자: 순서 무관, 이름으로 전달
describe_person(city="부산", name="이순신", age=45)

# 혼합: 위치 인자가 항상 먼저
describe_person("강감찬", city="대전", age=55)

# 에러: 키워드 인자 뒤에 위치 인자
# describe_person("강감찬", age=55, "대전")  ← SyntaxError

# 중복 전달 에러
# describe_person("홍길동", 30, "서울", name="홍길동")  ← TypeError

⚠️ 기본값의 함정 — 가변 객체 기본값

# 유명한 파이썬 버그: 가변 객체를 기본값으로 사용
def add_item_buggy(item, lst=[]):   # 리스트는 def 실행 시 한 번만 생성됨!
    lst.append(item)
    return lst

print(add_item_buggy("a"))   # ['a']    ← 기대: ['a']
print(add_item_buggy("b"))   # ['a', 'b'] ← 기대: ['b'] — 버그!
print(add_item_buggy("c"))   # ['a', 'b', 'c'] ← 누적됨!

# 이유: 기본값은 함수 정의 시점에 단 한 번 평가됨
# add_item_buggy.__defaults__ → (['a', 'b', 'c'],) ← 공유되는 객체!

# ✅ 올바른 패턴: None을 기본값으로, 함수 본문에서 생성
def add_item(item, lst=None):
    if lst is None:
        lst = []    # 호출마다 새 리스트 생성
    lst.append(item)
    return lst

print(add_item("a"))   # ['a']
print(add_item("b"))   # ['b']  ← 올바른 동작
print(add_item("c", [1, 2]))  # [1, 2, 'c']

# 불변 객체 기본값은 안전
def greet(name, greeting="안녕하세요"):  # 문자열은 불변 → 안전
    return f"{greeting}, {name}님!"

📦 *args — 가변 위치 인자

def my_sum(*args):
    # args는 추가 위치 인자를 담은 튜플
    print(type(args), args)
    return sum(args)

print(my_sum(1, 2, 3))        # <class 'tuple'> (1, 2, 3)  → 6
print(my_sum(10, 20))          # (10, 20)  → 30
print(my_sum())                # ()  → 0

# 앞에 일반 매개변수 혼합 가능
def make_tag(tag, *contents):
    inner = "".join(str(c) for c in contents)
    return f"<{tag}>{inner}</{tag}>"

print(make_tag("p", "안녕하세요"))
print(make_tag("ul", "<li>사과</li>", "<li>바나나</li>"))

# * 언패킹으로 이터러블을 위치 인자로 전달
nums = [1, 2, 3, 4, 5]
print(my_sum(*nums))    # my_sum(1, 2, 3, 4, 5)와 동일

# 여러 이터러블 병합
a = [1, 2, 3]
b = [4, 5, 6]
combined = [*a, *b]
print(combined)   # [1, 2, 3, 4, 5, 6]

📖 **kwargs — 가변 키워드 인자

def create_html_tag(tag, **attrs):
    # attrs는 키워드 인자를 담은 딕셔너리
    attr_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
    return f"<{tag} {attr_str}></{tag}>" if attr_str else f"<{tag}></{tag}>"

print(create_html_tag("a", href="https://example.com", target="_blank"))
print(create_html_tag("div", id="main", class_="container"))
print(create_html_tag("br"))   # attrs 없음

# ** 언패킹으로 딕셔너리를 키워드 인자로 전달
config = {"href": "https://example.com", "target": "_blank"}
print(create_html_tag("a", **config))

# 두 딕셔너리 병합 (Python 3.9+: | 연산자)
defaults = {"color": "blue", "size": 12}
overrides = {"size": 16, "weight": "bold"}
merged = {**defaults, **overrides}  # overrides가 기존 값 덮어씀
print(merged)  # {'color': 'blue', 'size': 16, 'weight': 'bold'}

# *args + **kwargs 조합: 모든 인자 받기
def log_call(*args, **kwargs):
    print(f"인자: {args}")
    print(f"키워드: {kwargs}")

log_call(1, 2, 3, name="홍길동", age=30)

🔒 위치 전용(/) vs 키워드 전용(*) 매개변수

# Python 3.8+ 구분자
# / 왼쪽: 위치 전용 (키워드로 전달 불가)
# * 오른쪽: 키워드 전용 (위치로 전달 불가)

def func(pos_only, /, normal, *, kw_only):
    print(f"pos_only={pos_only}, normal={normal}, kw_only={kw_only}")

func(1, 2, kw_only=3)          # ✅
func(1, normal=2, kw_only=3)   # ✅ normal은 키워드 가능
# func(pos_only=1, normal=2, kw_only=3)  ← TypeError: pos_only는 키워드 불가
# func(1, 2, 3)  ← TypeError: kw_only는 위치 불가

# 내장 함수가 위치 전용을 많이 씀
help(len)   # len(obj, /)  ← obj는 위치 전용
# len(obj=["a", "b"])  ← TypeError

# 완전한 매개변수 순서
def full_example(a, b, /, c, d, *args, e, f, **kwargs):
    pass
# a, b → 위치 전용
# c, d → 위치 또는 키워드
# *args → 가변 위치
# e, f → 키워드 전용
# **kwargs → 가변 키워드

🔗 인자 전달 방식 — Call by Object Reference

# 파이썬의 인자 전달: call by sharing (call by object reference)
# 함수는 객체에 대한 참조를 받음

def test_immutable(x):
    print(f"함수 안 id: {id(x)}")
    x = x + 1   # 새 int 객체 생성, 지역 변수 x가 새 객체를 가리킴
    print(f"재할당 후 id: {id(x)}")

n = 10
print(f"함수 밖 id: {id(n)}")
test_immutable(n)
print(f"함수 호출 후 n: {n}")   # 10 — 변하지 않음!
# int는 불변 → 함수 안에서 재할당해도 외부 변수에 영향 없음

def test_mutable(lst):
    print(f"함수 안 id: {id(lst)}")
    lst.append(99)   # 같은 객체를 수정!

my_list = [1, 2, 3]
print(f"함수 밖 id: {id(my_list)}")
test_mutable(my_list)
print(f"함수 호출 후 my_list: {my_list}")  # [1, 2, 3, 99] — 변경됨!
# list는 가변 → 함수 안에서 수정하면 외부에 영향

# 가변 객체를 수정하지 않으려면 복사본 전달
test_mutable(my_list.copy())   # 원본 보호
print(f"복사 후 my_list: {my_list}")  # [1, 2, 3, 99] — 변화 없음

⚠️ 자주 하는 실수

  • 가변 객체 기본값: def f(lst=[])는 모든 호출이 같은 리스트를 공유합니다. 반드시 lst=None으로 선언하고 함수 본문에서 생성하세요.
  • *args 뒤에 일반 매개변수: def f(*args, x)에서 x는 반드시 키워드로 전달해야 합니다. 위치로 전달하면 args가 먹어버립니다.
  • ** 언패킹에서 키 충돌: f(**d1, **d2)에서 d1과 d2에 같은 키가 있으면 TypeError. 미리 확인하거나 d1.update(d2)로 병합 후 전달하세요.

📝 정리 및 다음 강의 예고

  • 기본값은 함수 정의 시점에 한 번만 평가 — 가변 객체(list, dict)는 None으로 선언 후 함수 본문에서 생성.
  • *args: 추가 위치 인자를 튜플로. **kwargs: 추가 키워드 인자를 딕셔너리로.
  • / 왼쪽은 위치 전용, * 오른쪽은 키워드 전용 (Python 3.8+).
  • 파이썬은 call by object reference — 가변 객체 수정은 외부에 영향, 재할당은 영향 없음.

다음 강의: 반환값과 함수 설계 원칙 — 순수 함수와 부수 효과의 차이, 단일 책임 원칙을 배웁니다.

관련 주제

  • 위치 인자·키워드 인자
  • 가변 객체 기본값 함정
  • *args **kwargs
  • 위치전용·키워드전용 매개변수(/ *)
  • call by object reference
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 32강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...