문서 읽는 데 51분 · C1

C-1: 함수 — 같은 코드를 이름 붙여 다시 쓰기

목차 29
전체 22강 중 8강 · 파이썬 기초
난이도 · 입문

ℹ️프로그래밍 경험이 없어도 시작할 수 있는 파이썬 입문 — ‘개발 입문’을 먼저 들으면 더 수월해요. AI·데이터 분석으로 이어지는 출발점이에요.

안녕하세요, 홍순구 튜터입니다. 지난 시간엔 for 를 한 줄로 줄이는 컴프리헨션을 배우면서 카테고리 B(자료구조)를 잘 마쳤어요. 데이터를 담는 그릇은 이제 손에 익었죠.

그런데 지난 시간에 제가 슬쩍 흘린 말이 있어요. 우리, 비슷한 코드를 자꾸 반복해서 짰잖아요. "캡션에서 해시태그 뽑기" 를 한 캡션에서 하고, 또 다른 캡션에서 하고요. 만약 이걸 이름 붙여 묶어두고, 필요할 때마다 그 이름만 부를 수 있다면 어떨까요?

그게 바로 오늘 배울 함수(function) 예요. def 라는 키워드로 나만의 명령을 만들어, 같은 코드를 두 번 쓰지 않는 법을 익힙니다. 사실 우리가 지금까지 써온 print, len, sum, sorted 같은 것들도 다 누군가 미리 만들어 둔 함수였어요. 오늘은 그걸 직접 만들어 봅니다.

텍스트
                  오늘의 여정
        "같은 코드를 이름 붙여 다시 쓰기"

   같은 코드 반복 (지난 시간)
        │
        
   def 이름(매개변수): ... return        첫 함수 (Step 1)
        │
        
   여러 매개변수 · 여러 값 반환           튜플로 (Step 2)
        │
        
   기본값 · 키워드 인자                  호출을 편하게 (Step 3)
        │
        
   *args · **kwargs                     개수를 몰라도 (Step 4)
        │
        
   지역 vs 전역 (스코프)                 변수가 사는 동네 (Step 5)
        │
        
   가변 기본값 함정                      조심! (Step 6)
        │
        
   docstring · 좋은 함수                 설명 달기 (Step 7)
        │
        
   게시물 포맷 함수                      오늘 총동원 (Step 8)

💡 오늘 수업의 핵심 — "반복되는 코드를 def 로 이름 붙여 묶고, 필요할 때마다 그 이름만 불러 재사용한다"

🎯 학습 목표

  • def 로 함수를 정의하고, 매개변수로 값을 받아 return 으로 돌려주는 기본 구조를 익힙니다.
  • 기본값 인자·키워드 인자·*args/**kwargs 로 함수를 유연하게 부르는 법을 배웁니다.
  • 스코프(지역/전역)가변 기본값 함정 을 이해하고, docstring 으로 함수에 설명을 답니다.

Step 1: "같은 코드, 이름 붙여 한 번만" (def 첫 함수)

지난 시간을 떠올려 봅시다. 캡션에서 # 로 시작하는 해시태그를 뽑는 코드를 우리는 여러 번 짰어요. 비슷한 코드를 복사해서 또 쓰고, 또 쓰고요. 만약 단어 앞에 # 를 붙이는 일을 여기저기서 해야 한다면, 매번 "#" + word 를 다시 쓰는 건 번거롭겠죠.

이럴 때 그 일에 이름을 붙여 묶어두는 게 함수예요. 한 번 만들어 두고, 필요할 때마다 이름만 부르면 됩니다.

Python
# src/c_functions/c1_intro.py
# 지난 시간에 "캡션에서 해시태그 뽑기" 같은 코드를 두 번이나 짰어요.
# 한 번 정의해두고 이름만 부르면, 같은 코드를 두 번 쓰지 않아요.

def make_hashtag(word):
    return "#" + word


# 이제 이름만 부르면 됩니다.
print(make_hashtag("일상"))      # #일상
print(make_hashtag("여행"))      # #여행
print(make_hashtag("맛집"))      # #맛집

실행하면 이렇게 나와요.

텍스트
#일상
#여행
#맛집

이 짧은 코드에 함수의 모든 것이 들어 있어요. 한 줄씩 뜯어봅시다.

텍스트
   def  make_hashtag( word ):
   ─┬─  ─────┬─────  ─┬─
   만든다   함수 이름   매개변수(받을 값)

       return  "#" + word
       ──┬──   ───┬────
       돌려준다   돌려줄 값
  • def 는 "definition(정의)" 의 줄임말이에요. "지금부터 함수를 하나 만들게" 라는 신호죠.
  • make_hashtag 는 함수의 이름이에요. 나중에 이 이름으로 부릅니다.
  • 괄호 안 word매개변수 예요. 함수가 일할 때 밖에서 받아 오는 값을 담는 이름표죠.
  • 들여쓰기된 줄이 함수의 몸통 이에요. 지난 시간에 배운 그 들여쓰기 규칙, 여기서도 똑같이 "이 줄들이 함수 안쪽" 이라는 뜻이에요.
  • return 은 함수가 만든 결과를 밖으로 돌려주는 명령이에요.

그리고 make_hashtag("일상") 처럼 이름 뒤에 괄호를 붙여 값을 넣는 걸 함수 호출(call) 이라고 해요. "일상" 이 word 자리에 쏙 들어가서, "#" + "일상""#일상" 을 돌려받죠.

인사말도 함수로 묶어볼까요.

Python
def greet(name):
    return f"안녕하세요 @{name}님"


print(greet("민지"))             # 안녕하세요 @민지님
print(greet("재훈"))             # 안녕하세요 @재훈님
텍스트
안녕하세요 @민지님
안녕하세요 @재훈님

name 만 바꿔 부르면 매번 다른 인사가 나와요. 한 번 만든 틀을 여러 번 재사용하는 거죠. 사실 우리가 계속 써온 print(), len(), sum() 도 이렇게 누군가 미리 만들어 둔 함수였어요. 괄호에 값을 넣어 부르는 모양이 똑같죠? 이제 우리도 그런 도구를 직접 만들 수 있게 됐어요.

💡 한 줄 정리

함수는 def 이름(매개변수): 로 정의하고, 들여쓴 몸통에서 일을 한 뒤 return 으로 결과를 돌려줘요. 이름(값) 으로 호출하면 그 값이 매개변수 자리에 들어가요. 한 번 만들면 여러 번 재사용할 수 있죠.

🙋 학생 질문 — "튜터님, return 이랑 print 랑 같은 거 아니에요? 둘 다 결과가 보이잖아요."

아주 헷갈리기 쉬운 부분이에요. 둘은 완전히 달라요.

print 는 화면에 보여주기만 해요. 보여주고 끝이라, 그 값을 다시 쓸 수는 없어요. 반면 return 은 값을 돌려줘서, 그 결과를 변수에 담거나 다른 계산에 이어 쓸 수 있어요.

예를 들어 tag = make_hashtag("일상") 처럼 쓰면, return"#일상"tag 에 담겨요. 그다음 tag 를 또 다른 곳에 쓸 수 있죠. 만약 함수가 print 만 하고 return 이 없었다면, 화면에 보이긴 해도 그 값을 붙잡아 둘 수 없어요. "보여주기" 와 "돌려주기" 는 다른 일이에요.


Step 2: "여러 개를 받고, 여러 개를 돌려주기" (매개변수와 반환값)

Step 1 의 함수는 값을 하나만 받았어요. 그런데 함수는 매개변수를 여러 개 받을 수도 있어요. 쉼표로 나열하면 됩니다.

Python
# src/c_functions/c1_params.py

# 매개변수를 여러 개 받을 수 있어요.
def post_caption(username, text):
    return f"@{username}: {text}"


print(post_caption("민지", "오늘 날씨 좋다"))   # @민지: 오늘 날씨 좋다
텍스트
@민지: 오늘 날씨 좋다

username 자리에 "민지" 가, text 자리에 "오늘 날씨 좋다" 가 순서대로 들어갔어요. 이렇게 순서대로 넣는 값을 위치 인자 라고 해요. 자리 순서가 곧 어디로 들어갈지를 정하죠.

이번엔 거꾸로, 값을 여러 개 돌려주는 함수예요. 좋아요 수를 받아서, 숫자와 등급을 함께 돌려줄게요.

Python
# 값을 여러 개 돌려주면 튜플로 묶여 나와요 (지난 시간 튜플 기억하시죠).
def like_stats(likes):
    grade = "인기" if likes >= 1000 else "일반"
    return likes, grade


count, label = like_stats(8500)     # 튜플 언패킹으로 한 번에 받기
print(count, label)                 # 8500 인기
텍스트
8500 인기

return likes, grade 처럼 쉼표로 두 값을 돌려주면, 파이썬은 그 둘을 튜플 하나 로 묶어서 내보내요. 받는 쪽에서 count, label = ... 로 풀어서 한 번에 받았죠? 지난 카테고리에서 배운 튜플 언패킹이 여기서 다시 등장해요. 함수가 결과를 여러 개 돌려줄 때 정말 편한 방법이에요.

마지막으로 궁금한 거 하나. return 이 아예 없는 함수는 무엇을 돌려줄까요?

Python
# return 이 없는 함수는 None 을 돌려줘요.
def show(text):
    print(text)


result = show("hello")              # hello
print(result)                       # None
print(type(result))                 # <class 'NoneType'>
텍스트
hello
None
<class 'NoneType'>

show 는 화면에 출력만 하고 return 이 없어요. 이런 함수의 결과를 변수에 담으면 None 이 들어가요. None 은 "값이 없음" 을 뜻하는 파이썬의 특별한 값이에요. return 을 깜빡한 함수를 다른 계산에 쓰려다 None 때문에 당황하는 일이 종종 있으니, "돌려줄 게 있으면 꼭 return" 을 기억해 두세요.

💡 한 줄 정리

매개변수는 쉼표로 여러 개 받을 수 있고, 위치 순서대로 값이 들어가요. return a, b 처럼 여러 값을 돌려주면 튜플로 묶여 나와 언패킹으로 받을 수 있어요. return 이 없으면 None 이 돌아와요.

🙋 학생 질문 — "값을 두 개 돌려준다는데, 정말 두 개가 따로 나오는 거예요?"

정확히는 "두 개처럼 보이는 하나" 예요. return likes, grade 는 사실 (likes, grade) 라는 튜플 하나 를 돌려주는 거예요. 괄호를 생략했을 뿐이죠.

그래서 result = like_stats(8500) 처럼 받으면 result 에는 (8500, '인기') 라는 튜플 하나가 담겨요. 다만 우리가 count, label = ... 로 받으면 그 튜플이 자동으로 풀려서 각각 들어가는 거고요. "여러 값 반환" 은 결국 "튜플 하나 반환 + 언패킹" 의 조합이라고 이해하면 딱 맞아요.


Step 3: "기본값과 키워드로 더 편하게" (기본값 인자·키워드 인자)

함수를 부를 때마다 모든 값을 일일이 넣어야 한다면 번거로울 때가 있어요. 게시물을 만드는데 좋아요 수는 보통 처음엔 0 이잖아요. 매번 0 을 넣는 대신, 기본값 을 정해두면 생략할 수 있어요.

Python
# src/c_functions/c1_defaults.py

# 기본값 인자: likes 를 안 주면 0 으로 시작해요.
def make_post(caption, likes=0):
    return f"{caption} (좋아요 {likes})"


print(make_post("첫 게시물"))           # 첫 게시물 (좋아요 0)
print(make_post("인기글", 8500))        # 인기글 (좋아요 8500)
텍스트
첫 게시물 (좋아요 0)
인기글 (좋아요 8500)

likes=0 처럼 매개변수에 = 값 을 붙이면 그게 기본값이에요. make_post("첫 게시물") 처럼 likes 를 안 주면 자동으로 0 이 쓰이고, make_post("인기글", 8500) 처럼 주면 그 값을 써요. 자주 쓰는 기본값을 정해두면 호출이 훨씬 간결해지죠.

또 하나 편한 방법이 있어요. 값을 넣을 때 이름을 붙여서 넘기는 거예요. 이걸 키워드 인자라고 해요.

Python
# 키워드 인자: 이름을 붙여서 넘기면 순서가 달라도 돼요.
def profile(name, city, job):
    return f"{name} / {city} / {job}"


print(profile("민지", "서울", "디자이너"))             # 민지 / 서울 / 디자이너
print(profile(job="개발자", name="재훈", city="부산"))   # 재훈 / 부산 / 개발자
텍스트
민지 / 서울 / 디자이너
재훈 / 부산 / 개발자

첫 번째 호출은 위치 순서대로 넣었어요. 두 번째 호출을 보세요. job=, name=, city= 처럼 이름을 붙이니, 순서가 뒤죽박죽이어도 정확히 제자리로 들어갔어요. 매개변수가 많아지면 "이 값이 뭐였더라?" 헷갈리는데, 키워드 인자를 쓰면 코드만 봐도 의미가 또렷해져요.

위치 인자와 키워드 인자를 섞어 쓸 수도 있어요. 단, 위치 인자가 먼저 와야 해요.

Python
# 위치 인자가 앞, 키워드 인자가 뒤에 오면 섞어 쓸 수 있어요.
print(profile("승우", job="기획자", city="대구"))        # 승우 / 대구 / 기획자
텍스트
승우 / 대구 / 기획자

"승우" 는 위치로 name 에 들어가고, 나머지는 이름을 붙여 넣었죠.

💡 한 줄 정리

매개변수에 =값 을 붙이면 기본값이 되어, 호출할 때 생략하면 그 값이 쓰여요. 값에 이름= 을 붙여 넘기면 키워드 인자라 순서가 달라도 되고요. 섞어 쓸 땐 위치 인자가 먼저예요.

🙋 학생 질문 — "기본값이 있는 매개변수는 꼭 뒤에 둬야 하나요?"

네, 맞아요. def make_post(caption, likes=0) 처럼 기본값이 있는 매개변수(likes)는 기본값이 없는 매개변수(caption)보다 뒤에 와야 해요.

왜냐하면 우리가 make_post("첫 게시물") 처럼 부를 때, 파이썬은 앞에서부터 순서대로 값을 채워요. 만약 기본값 있는 게 앞에 있으면 "이 값이 기본값 자리에 들어간 건지, 다음 자리에 들어간 건지" 가 애매해져요. 그래서 파이썬은 아예 def f(x=0, y) 같은 정의를 에러로 막아둬요. "기본값 없는 것 먼저, 있는 것 나중" 으로 기억하면 편해요.


Step 4: "개수를 몰라도 받는다" (*args**kwargs)

지금까지는 매개변수 개수가 정해져 있었어요. 그런데 좋아요 수를 합산하는 함수를 만든다고 해봅시다. 어떤 날은 3 개, 어떤 날은 10 개… 개수가 그때그때 달라요. 이럴 때 매개변수 이름 앞에 별표 하나 를 붙이면, 들어온 값을 개수에 상관없이 다 받아요.

Python
# src/c_functions/c1_args_kwargs.py

# *args: 여분의 위치 인자를 튜플로 모아요.
def total_likes(*likes):
    return sum(likes)


print(total_likes(120, 8500, 320))    # 8940
print(total_likes(50, 50))             # 100
print(total_likes())                   # 0
텍스트
8940
100
0

*likes 는 들어온 위치 인자를 전부 튜플 로 모아줘요. 그러면 sum(likes) 로 한 번에 더할 수 있죠. 세 개를 넣든 두 개를 넣든 아예 안 넣든, 함수가 알아서 받아요. 흔히 이 이름을 *args 라고 쓰는데, "여러 개의 위치 인자(arguments)" 라는 관례적 이름일 뿐 다른 이름을 써도 돼요.

사실 우리가 늘 쓰던 print 도 이렇게 동작해요.

Python
# 우리가 써온 print 도 개수 제한 없이 받죠 — *args 의 예예요.
print("a", "b", "c")                   # a b c
텍스트
a b c

print 에 값을 몇 개를 넣든 다 받아서 출력하죠? 이게 바로 * 의 힘이에요.

별표가 두 개 면 어떨까요? 이번엔 이름이 붙은 키워드 인자들을 딕셔너리 로 모아요.

Python
# **kwargs: 여분의 키워드 인자를 딕셔너리로 모아요.
def make_profile(**info):
    return info


print(make_profile(name="민지", city="서울"))   # {'name': '민지', 'city': '서울'}
텍스트
{'name': '민지', 'city': '서울'}

name="민지", city="서울" 처럼 이름 붙여 넣은 값들이 {'name': '민지', 'city': '서울'} 라는 딕셔너리로 묶였어요. 키는 인자 이름, 값은 넣은 값이죠. 이 관례적 이름이 **kwargs("keyword arguments") 예요. 모인 딕셔너리는 지난 시간에 배운 .items() 로 돌 수 있어요.

Python
def describe(**info):
    for key, value in info.items():
        print(f"{key}: {value}")


describe(name="재훈", followers=1240, job="개발자")
텍스트
name: 재훈
followers: 1240
job: 개발자

별표 하나(*)는 위치 인자를 튜플로, 별표 둘(**)은 키워드 인자를 딕셔너리로. 이 둘만 기억하면 돼요.

💡 한 줄 정리

매개변수 앞에 * 를 붙이면 위치 인자를 개수 상관없이 튜플로 모으고, ** 를 붙이면 키워드 인자를 딕셔너리로 모아요. 관례 이름이 *args / **kwargs 예요. print* 방식이라 값을 몇 개든 받죠.

🙋 학생 질문 — "별표가 왜 하나, 둘로 나뉘어요? 헷갈려요."

모으는 대상이 다르기 때문이에요.

위치 인자(이름 없이 순서로 넣는 값)는 순서 가 중요해요. 순서가 있는 그릇이 튜플이죠. 그래서 별표 하나 * 가 위치 인자를 튜플로 모아요.

키워드 인자(name="민지" 처럼 이름 붙인 값)는 이름표(key)와 값(value) 의 짝이에요. 이름표로 값을 찾는 그릇이 딕셔너리죠. 그래서 별표 둘 ** 가 키워드 인자를 딕셔너리로 모아요.

"별 하나는 줄 세우기(튜플), 별 둘은 이름표 붙이기(딕셔너리)" 로 떠올리면 덜 헷갈려요. 지난 시간에 배운 튜플과 딕셔너리가 여기서 그대로 쓰여요.


Step 5: "변수가 사는 동네" (스코프)

함수를 만들다 보면 이런 의문이 들어요. "함수 안에서 만든 변수를 함수 밖에서도 쓸 수 있나?" 답을 코드로 확인해 봅시다.

Python
# src/c_functions/c1_scope.py

# 함수 안에서 만든 변수는 그 안에서만 살아요 (지역 변수).
def make_tag():
    tag = "#일상"
    return tag


print(make_tag())          # #일상
# print(tag)               # 함수 밖에선 tag 를 몰라요 — NameError 가 나요
텍스트
#일상

make_tag 안에서 만든 tag 는 그 함수 안에서만 살아요. 이런 변수를 지역 변수(local) 라고 해요. 함수 밖에서 print(tag) 를 하면 "그런 변수 모른다" 며 에러가 나요(그래서 주석 처리해 뒀어요). 변수마다 살 수 있는 동네가 정해져 있는데, 이 범위를 스코프(scope) 라고 불러요.

반대로, 함수 밖에서 만든 변수는 함수 안에서 읽을 수 있어요.

Python
# 함수 밖 변수는 함수 안에서 읽을 수 있어요 (전역 변수).
app_name = "Instagram"


def show_app():
    return f"앱 이름: {app_name}"


print(show_app())          # 앱 이름: Instagram
텍스트
앱 이름: Instagram

app_name 은 함수 밖에서 만든 전역 변수(global) 예요. 함수 안에서 따로 만들지 않아도 밖의 값을 가져다 읽을 수 있죠. 파이썬은 변수를 찾을 때 "함수 안(지역) → 함수 밖(전역) → 파이썬 기본 제공" 순으로 뒤져요. 그래서 함수 안에 app_name 이 없으니 밖에서 찾아 쓴 거예요.

그런데 함수 안에서 밖의 변수를 바꾸려 하면 조금 달라요.

Python
# 함수 안에서 밖의 변수를 바꾸려면 global 이 필요해요 (꼭 필요할 때만).
view_count = 0


def add_view():
    global view_count
    view_count = view_count + 1


add_view()
add_view()
print(view_count)          # 2
텍스트
2

global view_count 라고 선언해야 함수 안에서 밖의 view_count 를 바꿀 수 있어요. 이 선언이 없으면 파이썬은 함수 안에 새 지역 변수를 만들어 버려서, 밖의 값은 그대로예요.

⚠️ global 은 꼭 필요할 때만 쓰세요. 함수 밖 변수를 여기저기서 바꾸면 "이 값이 어디서 바뀌었지?" 를 추적하기 어려워져요. 보통은 값을 return 으로 돌려받아 밖에서 바꾸는 게 더 깔끔해요.

💡 한 줄 정리

함수 안에서 만든 변수는 지역 변수라 그 안에서만 살고, 함수 밖 변수는 전역 변수라 안에서 읽을 수 있어요. 안에서 전역 변수를 바꾸려면 global 선언이 필요한데, 남용하면 추적이 어려우니 되도록 return 을 쓰세요.

🙋 학생 질문 — "그럼 함수 안에서 계산한 값을 밖에서 쓰려면 무조건 global 써야 해요?"

아니에요, 오히려 반대예요. 대부분은 global 없이 return 으로 해결해요.

add_view 도 사실 이렇게 쓰는 게 더 좋아요. 함수가 return view_count + 1 로 새 값을 돌려주고, 밖에서 view_count = add_view(view_count) 처럼 받아서 바꾸는 거죠. 그러면 "값이 함수를 통해 들어가고 나온다" 가 또렷하게 보여요.

global 은 함수가 몰래 밖의 값을 건드리는 거라, 코드가 커지면 어디서 바뀌었는지 찾기 어려워요. "값은 매개변수로 받고, 결과는 return 으로 돌려준다" 를 기본으로 삼으면 훨씬 안전한 함수를 쓰게 돼요.


Step 6: "조심! 가변 기본값의 함정" (가변 기본값)

이번 Step 은 파이썬 입문자가 거의 다 한 번씩 빠지는 유명한 함정이에요. 미리 알아두면 나중에 몇 시간 디버깅을 아낄 수 있어요. 태그를 리스트에 차곡차곡 추가하는 함수를 만든다고 해봅시다. 기본값으로 빈 리스트를 줘봤어요.

Python
# src/c_functions/c1_mutable_default.py

# 함정! 기본값으로 빈 리스트를 쓰면 호출마다 그 리스트가 공유돼요.
def add_tag_bug(tag, tags=[]):
    tags.append(tag)
    return tags


print(add_tag_bug("일상"))     # ['일상']
print(add_tag_bug("여행"))     # ['일상', '여행']   비어 있어야 하는데 누적됐어요
print(add_tag_bug("맛집"))     # ['일상', '여행', '맛집']

실행하면 깜짝 놀랄 결과가 나와요.

텍스트
['일상']
['일상', '여행']
['일상', '여행', '맛집']

분명히 매번 빈 리스트에서 시작해서 ['여행'], ['맛집'] 만 나와야 할 것 같은데, 이전 값이 계속 누적 됐어요. 왜 이럴까요?

텍스트
 기본값 [] 은 함수를 "정의할 때" 딱 한 번만 만들어져요.
 그 한 개의 리스트를 모든 호출이 나눠 써요.

   add_tag_bug("일상")  ──┐
   add_tag_bug("여행")  ──┼──  같은 리스트 한 개에 계속 append
   add_tag_bug("맛집")  ──┘      ['일상', '여행', '맛집']

기본값 [] 는 함수를 정의하는 순간 한 번만 만들어져요. 그래서 호출할 때마다 새로 생기는 게 아니라, 그 하나의 리스트를 모두가 공유해요. 첫 호출에서 "일상" 을 넣고, 그 리스트가 그대로 남아서 다음 호출에서 "여행" 이 또 붙고… 그래서 누적된 거예요.

해결법은 간단해요. 기본값을 None 으로 두고, 함수 안에서 새 리스트를 만들면 돼요.

Python
# 해결: 기본값은 None 으로 두고, 함수 안에서 새 리스트를 만들어요.
def add_tag(tag, tags=None):
    if tags is None:
        tags = []
    tags.append(tag)
    return tags


print(add_tag("일상"))         # ['일상']
print(add_tag("여행"))         # ['여행']   매번 새로 시작!
print(add_tag("맛집"))         # ['맛집']
텍스트
['일상']
['여행']
['맛집']

이제 매번 새 리스트에서 시작하죠? tags=None 으로 두면 기본값은 그냥 None 한 개라 공유돼도 문제가 없고, if tags is None: 일 때 함수 안에서 새 리스트 [] 를 만드니 호출마다 깨끗하게 시작해요. 여기서 is None 은 "값이 None 인지" 확인하는 표현인데, None 을 비교할 땐 == None 보다 is None 을 쓰는 게 파이썬의 약속이에요.

이 규칙은 외워두면 좋아요. 리스트·딕셔너리·집합 같은 바꿀 수 있는(가변) 값은 기본값으로 직접 쓰지 말고, None 으로 두고 안에서 만든다.

💡 한 줄 정리

기본값은 함수를 정의할 때 한 번만 만들어져서, 리스트 같은 가변 값을 기본값으로 쓰면 모든 호출이 공유해 누적돼요. def f(x=None) 으로 두고 if x is None: x = [] 로 안에서 새로 만드는 게 안전한 패턴이에요.

🙋 학생 질문 — "왜 == None 이 아니라 is None 을 써요? 둘 다 같은 거 아닌가요?"

결과는 보통 같지만, 의미가 살짝 달라요.

== 는 "값이 같은가" 를 묻고, is 는 "정확히 같은 그 물건인가(같은 객체인가)" 를 물어요. None 은 파이썬 전체에 딱 하나만 존재하는 특별한 값이라, "이게 그 유일한 None 인가" 를 묻는 is None 이 의도에 더 정확해요.

게다가 is 비교가 더 빠르고, 어떤 특수한 경우엔 == 가 엉뚱하게 동작할 수도 있어서, 파이썬 커뮤니티는 None·True·False 를 비교할 땐 is 를 쓰자고 약속해 뒀어요. 지금은 "None 확인은 is None" 이라고만 기억해도 충분해요.


Step 7: "함수에 설명을 달자" (docstring 과 좋은 함수)

함수를 만들고 시간이 지나면, 정작 만든 나도 "이 함수가 뭐 하는 거였지?" 싶을 때가 있어요. 그래서 함수 바로 아래에 설명을 적어두는 자리가 있어요. 바로 docstring 이에요.

Python
# src/c_functions/c1_docstring.py

def make_hashtag(word):
    """단어 앞에 #을 붙여 해시태그 문자열을 만든다.

    word: 해시태그로 만들 단어 (예: "일상")
    반환: "#" 가 붙은 문자열 (예: "#일상")
    """
    return "#" + word


# docstring 은 함수의 __doc__ 에 저장돼요.
print(make_hashtag.__doc__)

print(make_hashtag("여행"))    # #여행
텍스트
단어 앞에 #을 붙여 해시태그 문자열을 만든다.

    word: 해시태그로 만들 단어 (예: "일상")
    반환: "#" 가 붙은 문자열 (예: "#일상")

#여행

함수 정의 바로 아래에 큰따옴표 세 개 """ 로 감싼 글이 docstring 이에요. 함수가 무슨 일을 하는지, 어떤 값을 받고 무엇을 돌려주는지를 적어둬요. 이 설명은 함수의 __doc__ 에 저장돼서 꺼내 볼 수 있고, REPL 에서 help(make_hashtag) 를 쳐보면 같은 설명이 깔끔하게 나와요.

docstring 만큼 중요한 게 좋은 함수를 만드는 습관 이에요. 두 가지만 기억해 두세요.

  • 하나의 함수는 하나의 일만make_hashtag 는 해시태그 만드는 일만, total_likes 는 좋아요 합산만 해요. 한 함수가 이것저것 다 하면 이름 붙이기도 어렵고 재사용도 힘들어요.
  • 이름은 동사로, 무슨 일을 하는지 드러나게make_hashtag, total_likes, add_view 처럼 이름만 봐도 무슨 일을 하는지 보이면, 나중에 그 함수를 쓸 때 docstring 을 안 봐도 짐작이 가요.

💡 한 줄 정리

docstring 은 함수 정의 바로 아래 """...""" 로 적는 설명이에요. __doc__ 이나 help() 로 꺼내 볼 수 있죠. 좋은 함수는 "하나의 일만" 하고, 이름만으로 무슨 일을 하는지 드러나게 동사로 짓는 게 좋아요.

🙋 학생 질문 — "그냥 # 주석이랑 docstring 이랑 뭐가 달라요?"

둘 다 설명이지만, 읽는 대상과 꺼내는 방법이 달라요.

# 주석은 코드 중간중간 "이 줄이 왜 이런지" 를 적는 메모예요. 코드를 직접 펼쳐 본 사람만 봐요. 반면 docstring 은 "이 함수 전체가 무슨 일을 하는지" 를 적는 공식 설명이라, help(함수이름) 이나 함수이름.__doc__ 으로 코드를 열지 않고도 꺼내 볼 수 있어요.

그래서 우리가 help(print) 를 쳤을 때 설명이 나오는 것도, print 에 docstring 이 달려 있기 때문이에요. "함수가 무슨 일을 하는지" 는 docstring 으로, "이 한 줄이 왜 이렇게 됐는지" 는 # 주석으로 적는다고 나눠 생각하면 좋아요.


Step 8: "오늘 배운 걸 총동원" (게시물 포맷 함수)

마지막으로 오늘 배운 걸 한자리에 모아 봅시다. 게시물 하나를 보기 좋은 여러 줄 문자열로 만들어 주는 함수예요. 위치 인자, 기본값, *args, **kwargs, docstring 이 한 함수에 다 들어가요.

Python
# src/c_functions/c1_format.py

def format_post(username, caption, likes=0, *hashtags, **meta):
    """게시물 한 건을 보기 좋은 여러 줄 문자열로 포맷한다."""
    lines = [f"@{username}", caption]
    if hashtags:
        lines.append(" ".join("#" + tag for tag in hashtags))
    lines.append(f"좋아요 {likes}개")
    for key, value in meta.items():
        lines.append(f"{key}: {value}")
    return "\n".join(lines)


post = format_post("민지", "오늘 카페에 왔어요", 8500, "일상", "카페", location="서울")
print(post)
텍스트
@민지
오늘 카페에 왔어요
#일상 #카페
좋아요 8500개
location: 서울

한 줄씩 따라가 봅시다. usernamecaption 은 위치 인자로 받고, likes=0 은 기본값 인자예요. 그 뒤 *hashtags 는 "일상", "카페" 를 튜플로 모으고, **metalocation="서울" 을 딕셔너리로 모아요.

함수 안에서는 줄을 담을 리스트를 만들고, 해시태그가 있으면(if hashtags:) # 를 붙여 한 줄로 합치고, 좋아요 수와 추가 정보를 차례로 붙여요. 마지막에 "\n".join(lines) 로 줄바꿈으로 이어 붙여 완성하죠. 오늘 배운 도구들이 하나의 함수 안에서 협력하는 모습이에요.

⚠️ 사실 실무에서 한 함수에 이렇게 모든 인자 종류를 다 섞어 쓰는 일은 드물어요. 오늘은 배운 걸 한눈에 모아 보려고 일부러 다 넣은 거예요. 평소엔 "이 함수는 무슨 일을 하나" 에 맞춰 꼭 필요한 인자만 받는 게 좋아요.

이 함수는 코드베이스의 tests/test_c1_functions.py 가 여러 경우로 검증해 둬서, 어떤 게시물을 넣어도 형식이 일정하게 나온다는 걸 확인해 뒀어요.

💡 한 줄 정리

위치 인자·기본값·*args·**kwargs·docstring 을 한 함수에 모아 게시물 포맷 함수를 완성했어요. 다만 실무에선 한 함수에 모든 인자 종류를 욱여넣기보다, 그 함수가 하는 일에 맞는 인자만 받는 게 좋아요.

🙋 학생 질문 — "이렇게 인자 종류를 다 섞어 쓰면 순서를 어떻게 기억해요?"

좋은 직감이에요. 그래서 실무에선 보통 다 섞지 않아요.

순서 규칙 자체는 정해져 있어요. 일반 매개변수 → 기본값 매개변수 → *args → **kwargs 순이에요. 오늘 format_post(username, caption, likes=0, *hashtags, **meta) 가 딱 이 순서죠. 파이썬이 이 순서를 강제해요.

하지만 순서를 외우려 애쓸 필요는 없어요. 정말 모든 종류가 필요한 경우는 드물거든요. 대부분의 함수는 위치 인자 몇 개, 혹은 기본값 한두 개면 충분해요. "필요한 만큼만 받는다" 를 기본으로 두면, 순서 때문에 헷갈릴 일이 거의 없어요.


마무리

오늘은 def 로 나만의 함수를 만드는 법을 배웠어요. 반복되던 코드에 이름을 붙여 묶고, 매개변수로 값을 받아 return 으로 돌려줬죠. 기본값과 키워드 인자로 호출을 편하게 하고, *args/**kwargs 로 개수를 몰라도 받고, 스코프와 가변 기본값 함정까지 짚었어요. 카테고리 C 의 첫 모듈, 정말 잘 따라오셨어요.

오늘 배운 핵심 세 가지

💡 하나: 함수는 def 이름(매개변수): 로 정의하고 return 으로 결과를 돌려줘요. 한 번 만들면 이름만 불러 여러 번 재사용할 수 있어요. print·len·sum 도 다 이렇게 만들어진 함수였죠.

💡 : 기본값 인자(likes=0)·키워드 인자(name="민지"*args(위치 인자를 튜플로)·**kwargs(키워드 인자를 딕셔너리로) 로 함수를 유연하게 부를 수 있어요.

💡 : 함수 안 변수는 지역, 밖 변수는 전역이에요(스코프). 그리고 리스트 같은 가변 값을 기본값으로 쓰면 호출마다 공유돼 누적되니, def f(x=None) 패턴으로 피해요.

다음 시간 예고

오늘 우리는 함수를 "만들어서 호출" 했어요. 그런데 파이썬에서는 함수도 일종의 이라, 다른 함수에 넘기거나 변수에 담을 수 있어요. 다음 시간엔 이름조차 없는 짧은 함수 람다(lambda) 와, 함수를 받아서 동작하는 map·filter·sorted(key=) 같은 도구를 배웁니다.

예를 들어 좋아요 순으로 게시물을 정렬할 때, "무엇을 기준으로 정렬할지" 를 함수로 넘겨줄 수 있어요. "함수를 값처럼 다룬다" 는 새로운 감각을 잡는 시간이 될 거예요. 기대해 주세요!


과제

오늘 배운 함수를 직접 손으로 만들어 봅시다. 눈으로 읽는 것과 직접 짜보는 건 완전히 달라요. 막히면 Step 으로 돌아가 코드를 다시 보세요.

[기초] 나만의 함수 세 개 만들기

오늘 배운 def·매개변수·return 으로 작은 함수들을 만들어 보세요.

  • 사용자 이름을 받아 환영 인사를 돌려주는 greet(username) 함수를 만듭니다 (예: "안녕하세요 @minji님, 환영해요!")
  • 단어를 받아 앞에 # 를 붙여 돌려주는 make_hashtag(word) 함수를 만듭니다
  • 좋아요 수를 받아 1000 이상이면 "인기", 아니면 "일반" 을 돌려주는 like_label(likes) 함수를 만듭니다
  • 세 함수를 여러 값으로 호출해 결과를 출력해 봅니다
  • (생각해보기) return 대신 함수 안에서 print 만 하면 무엇이 달라질까요? 직접 바꿔보고 차이를 느껴 보세요

[응용] 좋아요 통계 함수 만들기

좋아요 수를 개수 상관없이 받아 통계를 내는 함수를 만들어 보세요.

  • *args 로 좋아요 수를 여러 개 받는 like_stats(*likes) 함수를 만듭니다
  • 함수 안에서 합계(sum), 평균, 최고값(max)을 구해 한 문장으로 돌려줍니다
  • 평균은 f-string 의 :.1f 로 소수점 한 자리까지만 보여줍니다
  • 기본값 또는 키워드 인자로 통계 제목(예: label="좋아요")을 받아 결과 앞에 붙여 봅니다
  • (도전) 좋아요를 하나도 안 넣고 호출하면(like_stats()) 어떻게 되는지 확인하고, "데이터 없음" 같은 안내가 나오도록 처리해 보세요

[심화] 프로필 빌더 만들기

**kwargs 와 가변 기본값 함정 피하기를 함께 연습해 보세요.

  • 사용자 이름과 추가 정보(**extra)를 받아 프로필 딕셔너리를 만드는 build_profile(username, **extra) 함수를 만듭니다
  • 함수 안에서 {"username": username} 으로 시작해, 받은 추가 정보를 딕셔너리에 차례로 넣습니다
  • 태그 리스트(tags)도 받되, 기본값을 None 으로 두고 함수 안에서 새 리스트를 만들어 "신규" 태그를 추가합니다
  • build_profile 을 여러 번 호출해, 태그가 누적되지 않고 매번 새로 시작하는지 확인합니다
  • (도전) 만약 tags=[] 를 기본값으로 썼다면 어떤 문제가 생길지, 직접 바꿔 실행해 눈으로 확인해 보세요

생각해볼 주제

정답이 하나로 정해진 질문이 아니에요. 혼자 곰곰이 생각해 보거나, 함께 공부하는 동료와 이야기 나눠 보면 좋습니다.

1. 함수는 얼마나 잘게 쪼개야 할까?

오늘 우리는 "하나의 함수는 하나의 일만" 이라고 배웠어요. 그런데 게시물을 포맷하는 일을 "이름 줄 만들기", "해시태그 줄 만들기", "좋아요 줄 만들기" 처럼 더 잘게 함수로 나눌 수도 있겠죠.

그런데 너무 잘게 쪼개면 함수가 수십 개가 되어 오히려 따라가기 어려울 수도 있어요. 한 줄짜리 함수도 함수로 만들어야 할까요? "하나의 일" 이라는 게 정확히 어느 정도 크기를 말하는 걸까요? 함수를 나누는 기준에 대해 생각해 보세요.

2. 기본값은 왜 함수 정의 때 한 번만 만들어질까?

Step 6 에서 가변 기본값 함정을 봤어요. 기본값 [] 가 호출마다 새로 만들어지지 않고 한 번만 만들어져서 생긴 문제였죠.

그런데 만약 파이썬이 "기본값은 호출할 때마다 새로 만든다" 로 동작했다면 어땠을까요? 그 함정은 사라졌겠지만, 대신 무엇을 잃었을까요? 기본값을 한 번만 만드는 설계에는 어떤 장점이 있을지(예: 속도, 단순함) 상상해 보세요.

3. global 은 정말 나쁘기만 할까?

오늘 global 은 "꼭 필요할 때만, 되도록 return 을 쓰라" 고 배웠어요. 함수 밖 변수를 여기저기서 바꾸면 추적이 어렵다는 이유였죠.

그런데 세상의 모든 규칙엔 예외가 있어요. global 이 정말 유용하거나, 적어도 큰 문제가 안 되는 상황은 없을까요? 반대로, global 을 남용한 코드가 왜 위험한지 구체적인 상황을 떠올려 보세요. "왜 피해야 하는가" 를 알면 "언제 써도 되는가" 도 보여요.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안이에요. 먼저 스스로 충분히 손으로 짜본 뒤에 펼쳐 보세요. 정답을 외우는 게 아니라, "내가 짠 것과 어디가 다른지" 를 비교하는 게 진짜 공부예요.


🎯 [과제 1 예시답안] 나만의 함수 세 개 만들기

채점 포인트

항목 확인 핵심
매개변수 받기 def greet(username): 괄호 안 이름표로 값을 받음
반환 return f"..." 결과를 돌려줘 다시 쓸 수 있게
삼항 조건식 "인기" if likes >= 1000 else "일반" 한 줄로 값 고르기 (지난 카테고리)
호출 greet("minji") 이름 뒤 괄호에 값을 넣어 부름

풀이 예시

Python
# src/c_functions/c1_basic_solution.py
# [기초] 나만의 함수 만들기 — def · 매개변수 · return

def greet(username):
    return f"안녕하세요 @{username}님, 환영해요!"


def make_hashtag(word):
    return "#" + word


def like_label(likes):
    return "인기" if likes >= 1000 else "일반"


print(greet("minji"))
print(make_hashtag("여행"))
print(like_label(8500))
print(like_label(300))

실행 결과예요.

텍스트
안녕하세요 @minji님, 환영해요!
#여행
인기
일반

💡 튜터의 한마디: 세 함수 모두 "값을 받아서(매개변수) → 만들어서 → 돌려준다(return)" 는 같은 골격이에요. 마지막 생각거리, returnprint 로 바꿔 보셨나요? print 로 바꾸면 화면엔 똑같이 보이지만, tag = make_hashtag("여행") 처럼 결과를 변수에 담으려 하면 None 이 들어가요. "보여주기" 와 "돌려주기" 가 다르다는 걸 직접 느꼈다면 이 과제의 핵심을 잡은 거예요.

🎯 [과제 2 예시답안] 좋아요 통계 함수 만들기

채점 포인트

항목 확인 핵심
가변 인자 def like_stats(*likes, ...) 개수 상관없이 튜플로 모음
집계 sum(likes) / max(likes) / total / len(likes) 합계·최고·평균
소수점 포맷 f"{average:.1f}" 평균을 한 자리까지
키워드 인자 label="좋아요" 제목을 이름 붙여 받기
빈 입력 처리 if not likes: 데이터 없을 때 안내

풀이 예시

Python
# src/c_functions/c1_stats_solution.py
# [응용] 게시물 좋아요 통계 함수 — *args · 기본값 · 키워드 인자

def like_stats(*likes, label="좋아요"):
    if not likes:
        return f"{label}: 데이터 없음"
    total = sum(likes)
    average = total / len(likes)
    biggest = max(likes)
    return f"{label} 합계 {total}, 평균 {average:.1f}, 최고 {biggest}"


print(like_stats(120, 8500, 320, 1500))
print(like_stats(50, 50, label="첫 주 좋아요"))
print(like_stats())

실행 결과예요.

텍스트
좋아요 합계 10440, 평균 2610.0, 최고 8500
첫 주 좋아요 합계 100, 평균 50.0, 최고 50
좋아요: 데이터 없음

💡 튜터의 한마디: *likes 덕분에 네 개를 넣든, 두 개를 넣든, 아예 안 넣든 함수가 다 받아냈죠. 빈 입력(like_stats())일 때 if not likes: 로 먼저 걸러낸 게 중요해요. 이걸 빼면 total / len(likes) 에서 0 으로 나누다 에러가 나거든요. 그리고 label*likes 뒤에 있어서, 넘길 땐 꼭 label="..." 처럼 이름을 붙여야 해요. 가변 인자 뒤의 매개변수는 키워드로만 받는다는 점도 함께 느껴 보세요.

🎯 [과제 3 예시답안] 프로필 빌더 만들기

채점 포인트

항목 확인 핵심
**kwargs def build_profile(username, ..., **extra) 키워드 인자를 딕셔너리로
딕셔너리 조립 profile[key] = value 받은 정보를 차례로 넣기
가변 기본값 회피 tags=None + if tags is None: tags = [] 호출마다 새 리스트
누적 안 됨 확인 build_profile("jaehoon")tags=['신규'] 매번 새로 시작

풀이 예시

Python
# src/c_functions/c1_profile_builder_solution.py
# [심화] 프로필 빌더 — **kwargs 와 가변 기본값 함정 피하기

def build_profile(username, tags=None, **extra):
    if tags is None:
        tags = []
    tags.append("신규")
    profile = {"username": username, "tags": tags}
    for key, value in extra.items():
        profile[key] = value
    return profile


print(build_profile("minji", city="서울", job="디자이너"))
print(build_profile("jaehoon"))
print(build_profile("yuna", job="개발자"))

실행 결과예요.

텍스트
{'username': 'minji', 'tags': ['신규'], 'city': '서울', 'job': '디자이너'}
{'username': 'jaehoon', 'tags': ['신규']}
{'username': 'yuna', 'tags': ['신규'], 'job': '개발자'}

💡 튜터의 한마디: 두 번째 호출 build_profile("jaehoon") 의 결과를 보세요. tags['신규', '신규'] 가 아니라 ['신규'] 로 깨끗하게 나왔죠? tags=None 으로 두고 안에서 새 리스트를 만든 덕분이에요. 도전 과제대로 기본값을 tags=[] 로 바꿔 실행하면, 호출할 때마다 ['신규', '신규', '신규'] 처럼 누적되는 함정을 직접 보게 돼요. **extracity·job 처럼 이름 붙인 값들을 딕셔너리로 모아서, 프로필에 자연스럽게 합쳐줬고요.


🤔 [생각해볼 주제 1] 함수는 얼마나 잘게 쪼개야 할까?

문제 상황 요약

"하나의 함수는 하나의 일만" 이라고 배웠어요. 그렇다면 게시물 포맷을 "이름 줄", "해시태그 줄", "좋아요 줄" 처럼 더 잘게 나눠야 할까요? 한 줄짜리 함수까지 만들어야 할까요? "하나의 일" 은 정확히 어느 정도 크기일까요?

튜터의 가이드 및 해설

결론부터 말하면, "하나의 일" 은 줄 수가 아니라 하나의 이름으로 부를 수 있는 의미 단위 예요.

함수를 나누면 좋은 점은 분명해요. 이름이 곧 설명이 되고, 같은 일을 여러 곳에서 재사용할 수 있고, 문제가 생겼을 때 그 함수만 들여다보면 되죠. 그래서 "이 코드 덩어리에 이름을 붙일 수 있나?" 를 물었을 때 또렷한 이름이 떠오르면, 나눌 만한 가치가 있어요. make_hashtag_line 처럼요.

반대로 너무 잘게 쪼개면 부작용이 생겨요. 함수가 수십 개가 되면, 코드를 읽다가 함수 정의를 찾아 위아래로 계속 점프해야 해서 오히려 흐름이 끊겨요. 특히 딱 한 번만 쓰이고 이름도 애매한 한 줄짜리 함수는, 굳이 분리하지 않고 그냥 그 자리에 두는 게 더 읽기 좋을 때가 많아요.

좋은 기준은 두 가지예요. 첫째, "이 코드에 의미 있는 이름을 붙일 수 있는가." 둘째, "이걸 두 군데 이상에서 쓰거나, 따로 떼어 테스트하고 싶은가." 둘 중 하나라도 "그렇다" 면 함수로, 둘 다 "아니다" 면 그냥 두기. 줄 수보다 의미가 기준이에요.

💡 핵심을 한마디로

"함수를 나누는 기준은 줄 수가 아니라 '이름 붙일 수 있는 하나의 의미' 예요. 또렷한 이름이 떠오르고 재사용·테스트하고 싶으면 나누고, 한 번 쓰고 이름도 애매하면 그냥 두는 게 더 읽기 좋아요."

🤔 [생각해볼 주제 2] 기본값은 왜 함수 정의 때 한 번만 만들어질까?

문제 상황 요약

Step 6 의 가변 기본값 함정은, 기본값 [] 가 호출마다 새로 만들어지지 않고 정의할 때 한 번만 만들어져서 생긴 문제였어요. 만약 파이썬이 "기본값은 호출할 때마다 새로 만든다" 로 동작했다면 그 함정은 사라졌을 텐데, 대신 무엇을 잃었을까요?

튜터의 가이드 및 해설

이건 파이썬 설계의 트레이드오프를 들여다보는 좋은 질문이에요.

먼저 "한 번만 만든다" 의 장점은 속도와 단순함 이에요. 함수를 호출할 때마다 기본값을 새로 계산해야 한다면, 그 함수를 백만 번 부르면 기본값도 백만 번 다시 만들어야 해요. 기본값이 무거운 계산 결과라면 이 비용이 꽤 커지죠. 정의할 때 딱 한 번 만들어 두면, 이후 호출은 그 값을 그냥 가져다 쓰기만 하면 되니 빠르고 규칙도 단순해요.

또 파이썬의 일관된 철학과도 닿아 있어요. 파이썬에서 def 는 "함수라는 객체를 하나 만드는 일" 이에요. 그 함수 객체가 만들어질 때 기본값도 함께 함수에 묶여요. "정의 시점에 값이 정해진다" 는 규칙이 언어 전체에서 일관되게 동작하는 거죠.

물론 대가가 있어요. 바로 우리가 본 가변 기본값 함정이에요. 그래서 파이썬은 "그 대신 빠르고 일관된 규칙을 주고, 함정은 None 패턴으로 피하라" 는 선택을 한 거예요. 모든 설계엔 이렇게 무언가를 얻으면 무언가를 내주는 맞바꿈이 있어요. "왜 이렇게 동작하지?" 를 물으면, 그 뒤에 숨은 선택이 보여요.

💡 핵심을 한마디로

"기본값을 정의 때 한 번만 만드는 건 속도와 규칙의 단순함을 얻기 위한 선택이에요. 그 대가가 가변 기본값 함정이고, 그래서 None 패턴으로 피하는 거죠. 모든 설계엔 이런 맞바꿈이 있어요."

🤔 [생각해볼 주제 3] global 은 정말 나쁘기만 할까?

문제 상황 요약

global 은 "꼭 필요할 때만, 되도록 return 을 쓰라" 고 배웠어요. 함수 밖 변수를 여기저기서 바꾸면 추적이 어렵다는 이유였죠. 그런데 global 이 정말 유용하거나, 적어도 큰 문제가 안 되는 상황은 없을까요?

튜터의 가이드 및 해설

규칙을 외우는 것보다 "왜 그런 규칙이 있는지" 를 아는 게 훨씬 중요해요.

global 이 위험한 핵심 이유는 값이 어디서 바뀌었는지 추적하기 어렵다 는 거예요. 함수 열 개가 모두 같은 전역 변수를 바꿀 수 있으면, 그 값이 이상해졌을 때 열 군데를 다 의심해야 해요. 게다가 함수를 따로 떼어 테스트하기도 어려워져요. 함수의 결과가 "지금 전역 변수가 어떤 상태냐" 에 따라 달라지니까요. 이게 global 남용이 위험한 구체적인 이유예요.

그런데 큰 문제가 안 되는 경우도 있어요. 대표적으로 상수처럼 한 번 정하고 안 바꾸는 전역 값 이에요. app_name = "Instagram" 같은 건 모두가 읽기만 하니 안전해요. 사실 이건 global 선언 없이 그냥 읽는 거라, 우리가 Step 5 에서 본 안전한 사용이죠. 또 아주 작은 스크립트에서 "전체 실행 횟수" 같은 걸 잠깐 세는 정도라면 큰 문제가 안 될 수도 있어요.

정리하면, global 이 나쁜 게 아니라 "여러 곳에서 전역 값을 마구 바꾸는 것" 이 나쁜 거예요. 읽기만 하는 전역(상수)은 오히려 권장돼요. 그래서 더 나은 길은 보통 두 가지예요. 바꿔야 할 값은 매개변수로 받아 return 으로 돌려주기, 또는 안 바뀌는 설정값은 전역 상수로 두기. "왜 피하라고 하는가" 를 알면, "이 경우는 괜찮겠다" 도 스스로 판단할 수 있어요.

💡 핵심을 한마디로

"global 자체가 나쁜 게 아니라 '여러 곳에서 전역 값을 바꾸는 것' 이 추적을 어렵게 만들어요. 읽기만 하는 상수는 안전하고, 바뀌는 값은 매개변수와 return 으로 다루는 게 더 나은 길이에요."

전체 목록 파이썬 기초