덕 타이핑, 그리고 파이썬이 타입을 다루는 방식

덕 타이핑, 그리고 파이썬이 타입을 다루는 방식

“오리처럼 걷고, 오리처럼 꽥꽥거리면 — 그것은 오리다.”


덕 타이핑이란?

덕 타이핑은 객체의 타입을 클래스 선언이 아니라, 그 객체가 가진 메서드와 속성으로 판단하는 프로그래밍 철학입니다. 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이든 DogRobot이든, 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:

  • ROUGE
  • 행렬 곱의 네 가지 관점
  • How to teach your embedding model new words
  • classmethod vs staticmethod in Python
  • Powerful, and Thorough Unit Tests with pytest