/////

Python Typing: Protocol vs ABC for Plugin APIs, Decorators, and Narrowing Failure Modes

Python의 Protocol은 플러그인·데코레이터 중심 코드베이스에서 “구조적 서브타이핑”을 제공하므로 ABC보다 결합도를 낮출 수 있지만, 정적 타입 검사와 런타임 검사의 의미가 쉽게 어긋난다. 특히 @runtime checkable, ParamSpec 기반 데코레이터, call 프로토콜, TypeGuard/TypeIs 기반 narrowing, TypedDict와의 조합에서 mypy·pyright가 서로 다른 경고를 내거나 개발자가 실제 보장 범위를 과대평가

/////

Summary#

Python의 Protocol은 플러그인·데코레이터 중심 코드베이스에서 “구조적 서브타이핑”을 제공하므로 ABC보다 결합도를 낮출 수 있지만, 정적 타입 검사와 런타임 검사의 의미가 쉽게 어긋난다. 특히 @runtime_checkable, ParamSpec 기반 데코레이터, __call__ 프로토콜, TypeGuard/TypeIs 기반 narrowing, TypedDict와의 조합에서 mypy·pyright가 서로 다른 경고를 내거나 개발자가 실제 보장 범위를 과대평가하는 failure mode가 발생한다.

핵심 캡슐은 다음과 같다: Protocol은 “플러그인 계약의 정적 형태”를 표현하기 좋지만, 런타임 검증·데코레이터 래핑·narrowing 로직까지 동일하게 보장하지 않는다. ABC는 런타임 등록·상속 기반 계약에는 강하지만 플러그인 확장성과 duck typing 친화성은 떨어진다. 따라서 plugin/decorator-heavy 코드에서는 Protocol과 ABC를 대체재가 아니라 서로 다른 검증 층으로 분리해야 한다.

Key Points#

  • Protocol vs ABC의 기본 failure mode
  • Protocol은 구조적 서브타이핑이다. 어떤 클래스가 명시적으로 상속하지 않아도 필요한 메서드·속성을 갖추면 타입 검사기상 호환될 수 있다.
  • ABC는 명목적/상속 기반 계약에 가깝다. 플러그인이 ABC를 상속하거나 등록해야 하므로 런타임 확인은 더 직접적이지만, 외부 플러그인·동적 로딩·third-party 확장에는 더 침투적이다.
  • 실패 패턴:

    • 개발자가 Protocol을 “런타임 인터페이스”로 착각한다.
    • 실제 플러그인 객체는 필요한 속성을 런타임에 monkey patch, decorator, factory로 부여하지만 타입 검사기는 이를 추론하지 못한다.
    • 반대로 타입 검사기는 구조적으로 통과시키지만 런타임에서는 side effect, descriptor, property, optional method, callable wrapping 때문에 실패한다.
  • @runtime_checkable은 제한적이다

  • @runtime_checkable이 붙은 Protocol만 isinstance()/issubclass()에 사용할 수 있다.
  • 그러나 런타임 검사는 주로 “필요한 멤버 이름이 존재하는가” 수준이며, 타입 시그니처까지 정밀하게 검증하지 않는다.
  • Python 문서도 runtime_checkable protocol의 런타임 검사가 hasattr()류 검사에 가깝고, 정적 타입 검사와 다르다는 점을 경고한다.
  • failure mode:

    • isinstance(obj, SomeProtocol)True여도 메서드 인자·반환 타입이 실제로 맞는 것은 아니다.
    • 플러그인 시스템에서 이 결과를 신뢰해 호출하면 런타임 TypeError가 발생할 수 있다.
    • Python 3.12 이후 runtime-checkable protocol의 멤버 조회 방식 변경 등으로 기존 동적 객체가 다르게 판정될 수 있다.
  • 데코레이터가 callable 시그니처를 망가뜨리는 문제

  • plugin registry, middleware, command handler, event hook 시스템은 함수를 데코레이터로 감싸는 경우가 많다.
  • 데코레이터를 단순히 Callable[..., Any]로 타이핑하면 타입 안정성이 급격히 사라진다.
  • ParamSpecConcatenate는 데코레이터가 원래 함수의 인자 타입을 보존하거나 앞쪽 인자를 추가하는 패턴을 표현하기 위해 도입되었다.
  • failure mode:

    • 데코레이터가 *args: Any, **kwargs: Any 래퍼를 반환해 원래 플러그인 함수의 타입 정보를 잃는다.
    • functools.wraps는 런타임 메타데이터 보존에는 도움이 되지만 정적 타입 보존을 자동으로 보장하지 않는다.
    • mypy와 pyright가 복잡한 ParamSpec, overload, generic decorator 조합에서 서로 다른 추론 결과를 보일 수 있다.
    • Protocol__call__을 사용한 callable plugin contract와 decorator wrapper의 실제 반환 타입이 어긋난다.
  • Protocol.__call__은 플러그인 hook 표현에 유용하지만 취약하다

  • 예: class Hook(Protocol): def __call__(self, event: Event) -> Result: ...
  • 이 패턴은 함수, callable object, class instance plugin을 동일하게 받을 수 있어 plugin architecture에 적합하다.
  • failure mode:

    • decorator가 hook을 감싸면서 __call__의 정확한 signature를 잃는다.
    • sync/async callable이 섞이는 경우 Callable[..., Awaitable[T]]Callable[..., T]가 혼재된다.
    • callable object가 상태를 갖는 경우 구조적으로는 맞아도 lifecycle, init dependency, teardown contract는 표현하지 못한다.
    • Protocol은 “메서드가 있다”를 표현하지 “언제 호출 가능한 상태인지”를 표현하지 못한다.
  • TypeGuard / TypeIs narrowing은 Protocol 검증으로 오해되기 쉽다

  • TypeGuard는 사용자 정의 타입 좁히기를 가능하게 하지만, 함수 구현이 실제로 올바른지 타입 검사기가 증명하지 않는다.
  • TypeIs는 더 엄격한 narrowing semantics를 제공하지만, 여전히 predicate 구현의 런타임 정확성은 개발자 책임이다.
  • failure mode:

    • def is_plugin(x: object) -> TypeGuard[PluginProtocol]: ... 내부에서 hasattr(x, "run")만 검사하고 run의 signature를 확인하지 않는다.
    • 이 경우 타입 검사기는 이후 xPluginProtocol로 취급하지만 런타임 호출은 실패할 수 있다.
    • TypedDict와 조합할 때 key 존재 여부, optional key, required key, value type 검증이 섞이면서 narrowing이 실제 데이터 검증보다 강하게 보일 수 있다.
  • mypy vs pyright 차이 자체가 architectural risk가 될 수 있다

  • 두 도구 모두 Python typing 생태계의 주요 검사기지만, protocol variance, callable compatibility, ParamSpec inference, type narrowing, overload resolution에서 세부 동작이 다를 수 있다.
  • failure mode:

    • 로컬 개발자는 mypy만 통과, CI는 pyright만 통과 또는 반대.
    • library author는 한 checker의 behavior에 맞춰 public plugin API를 설계했지만 downstream 사용자는 다른 checker에서 오류를 본다.
    • “타입 힌트가 있다”는 사실이 “checker-portable contract가 있다”는 뜻은 아니다.
  • 실용적 설계 지침

  • 플러그인 외부 계약은 Protocol로 표현하되, 런타임 로딩 경계에서는 별도의 검증 함수를 둔다.
  • 런타임 검증은 @runtime_checkable만 믿지 말고, 필요한 경우 inspect.signature, 명시적 adapter, registration-time smoke test를 사용한다.
  • decorator는 가능하면 ParamSpec으로 원래 callable의 signature를 보존한다.
  • 플러그인 registry에는 raw callable 대신 typed wrapper/adaptor를 저장해 타입 경계를 한 번에 모은다.
  • ABC는 lifecycle, registration, default implementation, runtime identity가 중요한 내부 플러그인에는 여전히 유용하다.
  • public extension API는 Protocol + runtime validator + conformance tests 조합이 안전하다.
  • mypy와 pyright를 모두 지원하려면 복잡한 타입 트릭보다 단순한 Protocol, 명시적 type aliases, 작은 adapter layer가 더 유지보수 가능하다.

Cautions#

  • 이 초안은 현재 환경에서 실제 WebSearch/WebFetch 도구를 사용할 수 없어, 공개 웹 원문을 직접 fetch해 검증한 결과가 아니라 Python 공식 문서, PEP, mypy/pyright 문서로 알려진 공개 URL 기반의 보수적 정리다.
  • 특정 mypy·pyright 버전별 차이는 빠르게 변할 수 있다. 캡슐 확정 전에는 대상 버전의 release note와 playground/CI 재현 사례가 필요하다.
  • @runtime_checkable의 세부 동작은 Python 버전에 따라 달라질 수 있다. 특히 Python 3.12의 protocol member lookup 변화는 별도 확인이 필요하다.
  • TypeGuard/TypeIs의 unsoundness는 주로 “predicate 구현을 타입 검사기가 검증하지 않는다”는 구조적 한계에서 나온다. 특정 checker가 항상 잘못 narrowing한다는 의미는 아니다.
  • TypedDict와 Protocol의 조합은 실제 사례별로 달라진다. key-level validation, JSON schema validation, Pydantic/dataclass adapter 사용 여부에 따라 failure mode가 완화될 수 있다.
  • decorator-heavy architecture에서는 타입 이슈와 런타임 introspection 이슈가 섞인다. functools.wraps, __signature__, inspect.signature, static checker inference는 서로 다른 층이다.

Sources#

  • https://docs.python.org/3/library/typing.html#typing.Protocol
  • https://docs.python.org/3/library/typing.html#typing.runtime_checkable
  • https://peps.python.org/pep-0544/
  • https://peps.python.org/pep-0612/
  • https://peps.python.org/pep-0647/
  • https://docs.python.org/3/library/typing.html#typing.TypeGuard
  • https://docs.python.org/3/library/typing.html#typing.TypeIs
  • https://mypy.readthedocs.io/en/stable/protocols.html
  • https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
  • https://microsoft.github.io/pyright/#/type-concepts-advanced
  • https://microsoft.github.io/pyright/#/typed-libraries
  • https://typing.python.org/en/latest/spec/protocol.html

Sagwan Revalidation 2026-05-14T19:58:25Z#

  • verdict: ok
  • note: Protocol/ABC와 runtime_checkable 한계 설명은 최신 Python에서도 유효함

Sagwan Revalidation 2026-05-15T20:30:44Z#

  • verdict: ok
  • note: Protocol/ABC와 runtime_checkable 관련 핵심 주장은 현재도 유효함

Sagwan Revalidation 2026-05-16T20:42:25Z#

  • verdict: ok
  • note: Protocol/ABC와 runtime_checkable 관련 주장은 현재 관행과 부합함

Sagwan Revalidation 2026-05-17T21:08:42Z#

  • verdict: ok
  • note: Protocol/ABC 및 runtime_checkable 관련 핵심 주장은 현재도 유효함

Sagwan Revalidation 2026-05-18T21:32:40Z#

  • verdict: ok
  • note: 전반적 주장이 최신 Python typing 관행과 문서 내용에 부합함

Sagwan Revalidation 2026-05-19T21:53:58Z#

  • verdict: ok
  • note: 최근 typing 관행과 Python 3.12+ Protocol 주의점에 부합한다.

Sagwan Revalidation 2026-05-20T22:30:57Z#

  • verdict: ok
  • note: 최근 typing 동향과 3.12 runtime_checkable 주의점 모두 여전히 유효함

Sagwan Revalidation 2026-05-21T22:54:38Z#

  • verdict: ok
  • note: 최신 Python typing 관행과 runtime_checkable 주의점이 여전히 유효함

Sagwan Revalidation 2026-05-22T23:04:22Z#

  • verdict: ok
  • note: 전반적 주장과 Python 3.12+ Protocol 주의점이 여전히 유효함

Sagwan Revalidation 2026-05-23T23:05:46Z#

  • verdict: ok
  • note: Python 3.12+ Protocol 런타임 주의 등 핵심 내용이 현재도 유효함

Sagwan Revalidation 2026-05-24T23:17:14Z#

  • verdict: ok
  • note: Protocol/runtime_checkable/TypeGuard·TypeIs 관련 요지가 현재도 유효함

Sagwan Revalidation 2026-05-25T23:43:21Z#

  • verdict: ok
  • note: Protocol/ABC와 runtime_checkable 관련 핵심 주장은 현재도 유효함

Sagwan Revalidation 2026-05-27T00:13:44Z#

  • verdict: ok
  • note: 최근 typing 동향과 Python 3.12 Protocol 주의점까지 여전히 유효함

Sagwan Revalidation 2026-05-28T00:20:58Z#

  • verdict: ok
  • note: Python 3.12+ Protocol 런타임 검사 주의점까지 반영되어 유효함

Sagwan Revalidation 2026-05-29T00:56:47Z#

  • verdict: ok
  • note: 전반적 주장과 Python 3.12+ runtime_checkable 설명이 여전히 유효함

Sagwan Revalidation 2026-05-30T01:16:39Z#

  • verdict: ok
  • note: Python 3.12+ runtime_checkable 제한 등 핵심 내용이 여전히 유효함

Sagwan Revalidation 2026-05-31T01:32:10Z#

  • verdict: ok
  • note: Python 3.12+ Protocol 런타임 검사 주의 등 핵심 내용이 여전히 유효함

Sagwan Revalidation 2026-06-01T05:59:44Z#

  • verdict: ok
  • note: Protocol·runtime_checkable·3.12 변경 설명이 현재 관행과 부합함

Sagwan Revalidation 2026-06-02T06:54:53Z#

  • verdict: ok
  • note: 전반적 주장과 Python 3.12+ runtime_checkable 설명이 여전히 유효함

Sagwan Revalidation 2026-06-03T07:36:24Z#

  • verdict: ok
  • note: Python 3.12+ runtime_checkable 설명 등 현재 관행과 부합함

Sagwan Revalidation 2026-06-04T08:07:02Z#

  • verdict: ok
  • note: Python 3.12+ Protocol 런타임 검사 설명까지 현재 practice와 부합함

Sagwan Revalidation 2026-06-05T08:28:11Z#

  • verdict: ok
  • note: Protocol/runtime_checkable 한계와 3.12 변화 설명이 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1