Application/Python

[Python] 파이썬 타입 힌팅, 모르고 쓰면 손해예요

devsalix 2026. 5. 26. 09:56
728x90

파이썬 타입 힌팅, 모르고 쓰면 손해예요

지난 글에서 제너레이터까지 보고 나면 슬슬 "고급 문법은 다 비슷한 느낌"이라는 생각이 드실 수 있어요. 그래서 이번엔 좀 분위기를 바꿔서, 문법보다는 코드가 얼마나 읽기 좋은가에 영향을 주는 도구 하나를 들고 왔습니다.

혹시 이런 경험 있으세요? 다른 사람이 짠 코드를 보는데, 함수 하나가 떡 하니 있어요.

def process(data, config):
    ...

 

data가 뭐예요? 리스트인가요, 딕셔너리인가요? config는요? 결국 함수 안으로 들어가서 한참 추적해야 알 수 있죠. 본인이 한 달 전에 짠 코드를 다시 봐도 똑같거든요. 저도 그랬어요.

이걸 한 줄로 해결해주는 게 오늘 이야기할 파이썬 타입 힌팅이에요.


파이썬 타입 힌팅이 뭐예요?

쉽게 말하면, 변수랑 함수에 라벨을 붙여주는 일이에요.

약병에 "두통약"이라고 써 붙이는 거랑 비슷합니다. 라벨이 없어도 약은 약이에요. 먹는 데 지장이 없죠. 근데 라벨이 붙어 있으면 다른 사람도 헷갈리지 않고, 본인도 두 달 뒤에 안 까먹잖아요.

타입 힌트가 딱 그 라벨이에요. "이 변수에는 정수가 들어가요", "이 함수는 문자열을 받아서 숫자를 돌려줘요" 같은 안내문인 거죠.


가장 단순한 형태부터

변수 옆에 콜론(:)을 붙이고 타입을 적어주면 끝이에요.

# 변수 이름 옆에 콜론, 그리고 타입을 적어요
name: str = "철수"      # 이 자리에는 문자열이 들어와요
age: int = 30           # 이 자리에는 정수가 들어와요
height: float = 175.5   # 이 자리에는 실수가 들어와요

 

이게 전부입니다. 진짜 별거 없어요.


함수에 라벨 달기 (가장 많이 쓰는 형태)

사실 변수 힌트는 잘 안 써요. 진짜 자주 쓰는 건 함수에 다는 거거든요.

# 매개변수마다 : 타입, 반환값은 -> 타입
def add(a: int, b: int) -> int:
    return a + b

result = add(3, 5)
print(result)  # 8

 

a: int는 "a는 정수예요", -> int는 "반환값도 정수예요"라는 뜻이에요. 함수 시그니처만 봐도 뭐가 들어가고 뭐가 나오는지 한눈에 보이잖아요.

반환값이 없는 함수는 -> None을 써줍니다.

def print_log(msg: str) -> None:  # 아무것도 안 돌려주면 None
    print(msg)

리스트나 딕셔너리는 어떻게 해요?

파이썬 3.9 이상이면 그냥 소문자로 쓰면 됩니다.

scores: list[int] = [90, 85, 70]                   # 정수가 든 리스트
ages: dict[str, int] = {"철수": 20, "영희": 22}    # 키는 문자열, 값은 정수

def average(nums: list[int]) -> float:
    return sum(nums) / len(nums)

print(average(scores))  # 81.66666666666667

 

가끔 옛날 코드에서 from typing import List, Dict 이렇게 임포트해서 List[int] 같은 걸 쓰는 게 보일 거예요. 그건 3.8 이전 버전 호환용이에요. 요즘 환경이라면 그냥 소문자 list[int] 쓰시면 됩니다.


"값이 없을 수도 있어요"는 어떻게 표현해요?

함수가 가끔은 None을 돌려줄 때가 있잖아요. 그럴 때 쓰는 게 X | None 형태예요. (파이썬 3.10 이상에서 가능합니다.)

def find_age(name: str, ages: dict[str, int]) -> int | None:
    return ages.get(name)  # 없으면 None 반환

result = find_age("민수", {"철수": 20, "영희": 22})
if result is None:
    print("못 찾았어요")
else:
    print(f"{result}살")

 

int | None은 "정수 아니면 None"이라는 뜻이에요. 옛날엔 Optional[int]라고 썼는데, 요즘은 int | None이 훨씬 깔끔해서 많이 씁니다.


잠깐, 근데 강제는 아니에요

여기서 좀 충격적인 사실 하나 알려드릴게요.

age: int = "스무살"  # 어... 에러 안 나요

 

이거 실행됩니다. 에러 안 나요. 진짜예요.

처음 이걸 알게 되면 "그럼 왜 써요?" 하는 반응이 자동으로 나오시거든요. 저도 그랬어요.

답은 이렇습니다.

  • 에디터가 똑똑해져요: VS Code나 파이참이 자동완성을 훨씬 잘해주고, 잘못된 사용엔 빨간 줄을 그어줘요
  • 읽는 사람이 편해요: 협업할 때 함수 시그니처만 봐도 뭐가 들어가는지 바로 알아요
  • mypy로 미리 잡을 수 있어요: 실행 전에 타입 오류를 잡아주는 도구가 따로 있어요

즉, 타입 힌트는 사람과 도구를 위한 안내문이에요. 파이썬 자체는 신경 안 씁니다.


mypy로 정적 검사 살짝 맛보기

말로만 하면 와닿지 않으니까, 진짜 검사하는 방법도 짧게 보여드릴게요.

pip install mypy
mypy my_script.py

 

이렇게 하면 타입이 안 맞는 곳을 콘솔에서 알려줍니다. 실행하기 전에 미리 잡을 수 있다는 게 핵심이에요. 코드 양이 늘어날수록 진짜 유용해지거든요. 협업 프로젝트에선 거의 필수에 가깝습니다.


너무 자세히 적으면 오히려 독이에요

가끔 욕심내서 이런 거 적으시는 분들이 있는데요.

def handle(x: Callable[[int, str], dict[str, list[tuple[int, str]]]]) -> None:
    ...

 

이건 라벨이 아니라 암호문이에요. 읽기 더 힘들어집니다.

타입 힌트는 함수 시그니처와 핵심 변수에 우선 달고, 모든 줄에 다 붙일 필요는 없어요. 너무 복잡해지면 차라리 주석으로 풀어 쓰는 게 낫습니다.


직접 해보기

지난 글들에서 만들었던 BMI 함수에 타입 힌트를 한번 달아보세요.

# 힌트 없는 원본
def bmi(weight, height):
    return weight / (height ** 2)

# 힌트 추가
def bmi(weight: float, height: float) -> float:
    return weight / (height ** 2)

print(bmi(70.0, 1.75))  # 22.857142857142858

 

위아래를 비교해보시면, 라벨이 붙은 쪽이 "아, 실수 두 개 받아서 실수 돌려주는구나"가 바로 보이잖아요. 이게 파이썬 코드 품질을 올리는 가장 작고 확실한 습관 중 하나예요.

타입 힌팅으로 코드 읽히는 정도를 끌어올렸으니, 이제 코드가 빨라지는 다른 차원이 남아 있어요. 다음 글에선 비동기 프로그래밍, asyncawait 이야기를 해볼 거예요. 네트워크 요청 여러 개를 동시에 던지는, 좀 신기한 세상입니다.


 


제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀

 

 
728x90