덕 타이핑, 그리고 파이썬이 타입을 다루는 방식
덕 타이핑, 그리고 파이썬이 타입을 다루는 방식
“오리처럼 걷고, 오리처럼 꽥꽥거리면 — 그것은 오리다.”
덕 타이핑이란?
덕 타이핑은 객체의 타입을 클래스 선언이 아니라, 그 객체가 가진 메서드와 속성으로 판단하는 프로그래밍 철학입니다. Python, JavaScript, Ruby 같은 동적 타입 언어에서 자주 볼 수 있는 접근 방식이에요.
타입을 명시적으로 확인하지 않고, 필요한 동작을 그냥 호출합니다. 해당 메서드나 속성이 존재하면 성공, 없으면 런타임 에러가 납니다.
class Duck:
def sound(self):
return "꽥꽥!"
class Dog:
def sound(self):
return "멍멍!"
class Robot:
def sound(self):
return "삐빅!"
def make_sound(animal):
# 타입 확인 없이 그냥 호출
print(animal.sound())
make_sound(Duck()) # 꽥꽥!
make_sound(Dog()) # 멍멍!
make_sound(Robot()) # 삐빅!
Duck이든 Dog든 Robot이든, sound() 메서드만 있으면 동일하게 취급됩니다. “이 객체가 무엇인가”보다 “이 객체가 무엇을 할 수 있는가” 에 집중하는 셈이죠.
ABC는 덕 타이핑을 깨뜨리나?
결론부터 말하면 꼭 그렇지는 않습니다. ABC를 어떻게 사용하느냐에 따라 달라집니다.
isinstance() 강제 체크 → 덕 타이핑 깨짐
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def sound(self): pass
def make_sound(animal):
if not isinstance(animal, Animal): # 타입 직접 확인!
raise TypeError("Animal이 아님")
print(animal.sound())
make_sound(Robot()) # ❌ sound()가 있어도 거부됨
ABC는 명세 역할만, 체크 없음 → 덕 타이핑 유지
함수 안에서 타입을 체크하지 않으면 ABC를 써도 덕 타이핑은 그대로 동작합니다. ABC가 하는 일은 “이 메서드는 꼭 구현해야 해”라는 개발자 간의 약속일 뿐입니다.
register()로 절충
Animal.register(Robot) # Robot을 Animal로 등록
print(isinstance(Robot(), Animal)) # ✅ True — 상속 없이도 통과
덕 타이핑의 유연함을 유지하면서 타입 체계도 일부 활용하는 절충안입니다. 단, 개발자가 수동으로 등록해야 한다는 번거로움이 있습니다.
| 상황 | 덕 타이핑 |
|---|---|
ABC + isinstance() 강제 체크 | ❌ 깨짐 |
| ABC만 정의, 체크 없이 호출 | ✅ 유지 |
ABC + register() 활용 | 〰 절충 |
타입 힌트는 덕 타이핑과 무관하다
타입 힌트는 런타임에 완전히 무시됩니다. animal: Duck이라고 써도 파이썬 인터프리터는 실행 중에 이를 전혀 검사하지 않아요.
def make_sound(animal: Duck) -> str:
return animal.sound()
make_sound(Robot()) # ✅ 아무 문제 없이 실행됨
타입 힌트가 실제로 하는 일은 다음과 같습니다.
- 정적 분석: mypy, pyright 같은 타입 체커가 코드 실행 전에 경고를 띄워줌
- IDE 지원: 자동완성과 경고 표시에 활용됨
- 문서화: 코드를 읽는 사람에게 의도를 전달하는 역할
mypy를 CI에 붙여 강제하면 실질적으로 덕 타이핑을 제한하는 효과가 생기지만, 이는 어디까지나 개발 단계의 검사입니다. 런타임 동작은 여전히 덕 타이핑입니다.
Protocol — 타입 힌트를 위한 덕 타이핑
mypy를 사용하면서도 덕 타이핑의 철학을 유지하고 싶을 때 Protocol을 씁니다. Protocol의 핵심 존재 이유는 바로 타입 힌트와 정적 분석입니다.
from typing import Protocol
class Soundable(Protocol):
def sound(self) -> str: ...
def make_sound(animal: Soundable) -> str:
# "sound() 메서드만 있으면 OK"
return animal.sound()
class Robot: # Soundable을 상속하지 않아도
def sound(self):
return "삐빅!"
make_sound(Robot()) # ✅ mypy 통과, 런타임도 통과
이를 구조적 서브타이핑(Structural Subtyping) 이라고 부릅니다. 상속 관계 없이 “이 메서드를 가지고 있으면 이 타입으로 간주”하는 방식으로, 타입 힌트 세계에서 덕 타이핑의 정신을 그대로 구현합니다.
@runtime_checkable — 런타임에서도 사용하기
from typing import Protocol, runtime_checkable
@runtime_checkable
class Soundable(Protocol):
def sound(self) -> str: ...
print(isinstance(Robot(), Soundable)) # ✅ True
# 메서드 존재 여부를 자동으로 확인
ABC의 register()와 비슷하지만, 개발자가 수동으로 등록할 필요 없이 메서드의 존재만으로 자동으로 판단합니다.
| 방식 | 덕 타이핑 | 정적 분석 | 런타임 isinstance |
|---|---|---|---|
| 순수 덕 타이핑 | ✅ | ❌ | ❌ |
ABC + register() | 〰 | ❌ | ✅ (수동) |
| Protocol | ✅ | ✅ | ❌ |
Protocol + runtime_checkable | ✅ | ✅ | ✅ (자동) |
정리
- 덕 타이핑은 “무엇인가”보다 “무엇을 할 수 있는가”로 타입을 판단한다.
- ABC는 사용 방식에 따라 덕 타이핑을 깨뜨릴 수도, 유지할 수도 있다.
- 타입 힌트는 런타임에 무시되므로 덕 타이핑에 직접 영향을 주지 않는다.
- Protocol은 타입 힌트 + 정적 분석 환경에서 덕 타이핑 철학을 유지하는 수단이다.
Enjoy Reading This Article?
Here are some more articles you might like to read next: