본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 덕 타이핑(Duck Typing)과 ABC의 3가지 결정적 차이와 설계 해결 방법

by Papa Martino V 2026. 4. 2.
728x90

Duck Typing
Duck Typing

 

 

파이썬(Python)의 설계 철학을 관통하는 가장 유명한 문구는 "오리처럼 걷고 오리처럼 꽥꽥거린다면, 그것은 오리다"라는 덕 타이핑(Duck Typing)입니다. 하지만 프로젝트의 규모가 커지고 협업 인원이 늘어남에 따라, 이러한 동적 타이핑의 유연함은 오히려 '런타임 에러'라는 부메랑이 되어 돌아오기도 합니다. 이를 해결하기 위해 파이썬 2.6부터 도입된 것이 바로 추상 기반 클래스(Abstract Base Classes, ABC)입니다. 본 포스팅에서는 실무 개발자가 마주하는 "유연한 설계"와 "엄격한 인터페이스" 사이의 갈등을 해결하기 위해, 덕 타이핑과 ABC의 내부 동작 원리를 심층 분석합니다. 또한 7가지 실전 예제를 통해 언제 어떤 방식을 선택해야 성능과 유지보수라는 두 마리 토끼를 잡을 수 있는지 명확한 가이드를 제시합니다.


1. 동적 유연성과 정적 명시성의 충돌

덕 타이핑은 객체의 실제 타입보다 객체가 어떤 메서드를 가졌는가에 집중합니다. 반면, ABC는 객체가 어떤 부류(계층)에 속하는가를 명시적으로 정의합니다. 이는 단순한 취향의 차이가 아니라, 코드의 예측 가능성과 확장성을 결정짓는 아키텍처의 선택 문제입니다.


2. Duck Typing vs ABC 결정적 차이 요약

두 방식의 핵심 메커니즘과 장단점을 표를 통해 비교해 보겠습니다.

비교 항목 덕 타이핑 (Duck Typing) 추상 기반 클래스 (ABC)
핵심 철학 동적 행동(Behavior) 중시 명시적 계약(Contract) 중시
검증 시점 런타임(메서드 호출 시) 인스턴스 생성 시 (실행 전 단계)
결합도 매우 낮음 (상속 불필요) 중간 (상속 또는 등록 필요)
IDE 지원 제한적 (힌트 부족) 강력함 (자동 완성 및 경고)
사용 적합성 빠른 프로토타이핑, 범용 라이브러리 대규모 시스템, 엄격한 API 설계

3. 실무 해결을 위한 7가지 Sample Examples

개발자가 실무에서 두 개념을 어떻게 전략적으로 혼합하여 사용하는지 보여주는 예시입니다.

Example 1: 전형적인 덕 타이핑 (유연한 함수 설계)

특정 클래스를 상속받지 않아도 read() 메서드만 있다면 무엇이든 수용합니다.

def process_data(source):
    # source가 파일인지, 메모리 스트림인지 묻지 않습니다.
    # 단지 read() 행동이 가능한지만 봅니다.
    data = source.read()
    return f"Processed: {data}"

class MockSource:
    def read(self): return "Mock Data"

print(process_data(MockSource()))

Example 2: ABC를 이용한 인터페이스 강제 (Fail-Fast 해결)

필수 메서드를 구현하지 않은 자식 클래스의 인스턴스 생성을 원천 차단합니다.

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class KakaoPay(PaymentProcessor):
    def pay(self, amount):
        print(f"Paying {amount} via Kakao")

# p = PaymentProcessor() # TypeError 발생
# class IncompletePay(PaymentProcessor): pass
# i = IncompletePay() # TypeError 발생 (pay 미구현)

Example 3: isinstance()와 ABC의 조합 (타입 안전성)

덕 타이핑의 모호함을 해결하기 위해 특정 '능력'을 가진 객체인지 ABC로 검사합니다.

from collections.abc import Iterable

def get_first_item(obj):
    if isinstance(obj, Iterable):
        return next(iter(obj))
    raise TypeError("Object is not iterable")

print(get_first_item([10, 20])) # 10

Example 4: 가상 서브클래스 등록 (Virtual Subclass)

직접 상속받지 않은 외부 라이브러리 클래스를 ABC의 자식으로 인정하는 마법 같은 방법입니다.

class MyInterface(ABC):
    @abstractmethod
    def execute(self): pass

class ExternalTool:
    def execute(self): print("Tool Running")

# 상속 없이 MyInterface의 일원으로 등록
MyInterface.register(ExternalTool)

tool = ExternalTool()
print(isinstance(tool, MyInterface)) # True

Example 5: 덕 타이핑의 한계 - 메서드 이름 충돌 해결

이름은 같지만 의미가 다른 메서드(예: draw() - 그리기 vs 인출하기)를 구분할 때 ABC를 활용합니다.

class Artist(ABC):
    @abstractmethod
    def draw(self): pass

class BankAccount(ABC):
    @abstractmethod
    def draw(self): pass

# 상속을 통해 명확한 의도(Intent)를 파악할 수 있음

Example 6: 프로토콜(Protocol)을 이용한 정적 덕 타이핑

Python 3.8+의 typing.Protocol을 사용하여 덕 타이핑의 유연함과 ABC의 검증 능력을 합칩니다.

from typing import Protocol

class Flyer(Protocol):
    def fly(self) -> str: ...

def lift_off(entity: Flyer):
    return entity.fly()

class Bird:
    def fly(self): return "Flap flap"

print(lift_off(Bird())) # 정적 분석기(Mypy)에서 Bird를 Flyer로 인정함

Example 7: 플러그인 아키텍처 설계

사용자가 자유롭게 기능을 확장하되, 시스템 규격은 맞추게 하는 실무 패턴입니다.

class Plugin(ABC):
    @abstractmethod
    def setup(self): pass

def load_plugins(plugin_list):
    for p in plugin_list:
        if isinstance(p, Plugin):
            p.setup()
        else:
            print(f"Skipping invalid plugin: {type(p)}")

4. 결론: 언제 무엇을 선택해야 할까?

프로젝트 초기나 소규모 스크립트, 혹은 다양한 객체를 범용적으로 처리해야 하는 라이브러리를 개발할 때는 덕 타이핑의 유연함이 최선입니다. 하지만 대규모 엔터프라이즈 시스템이나, API를 제공하여 타인이 내 코드를 확장해야 하는 상황이라면 ABC를 통해 명확한 계약을 제시하는 것이 유지보수 비용을 낮추는 지름길입니다.


5. 참고 문헌 및 자료 출처

  • Python PEP 3119 – Introducing Abstract Base Classes.
  • Luciano Ramalho. "Fluent Python" (Chapter 11: Interfaces: From Protocols to ABCs).
  • Python Documentation. "abc — Abstract Base Classes".
  • Real Python. "Inheritance and Composition: A Python OOP Guide".
728x90