파이썬과 인터페이스: 프로토콜에서 ABC까지

휴먼스케이프

안녕하세요. 휴먼스케이프의 개발자 Bruno입니다.

이 번 포스팅에선 루시아누 하말류의 저서 Fluent Python의 11장 [인터페이스: 프로토콜에서 ABC까지]의 내용을 다룹니다.

서론

파이썬에서는 C++이나 JAVA와 같이 강타입 언어들과는 다르게 덕타이핑을 통해 객체지향의 인터페이스와 같은 기능을 수행할 수 있습니다. 예를 들어서 len()이라는 내장함수에서 사용할 수 있는 객체를 만들고 싶다면, len에서 호출하는 __len__()이라는 던더메서드를 정의하면 되는 식이죠.

__len__() 던더메서드만 구현한다면, 아래 예시처럼 새로 정의한 클래스에서도 len() 함수에서 사용할 수 있도록 만들 수 있습니다.

class Foo:
    def __len__():
        return 0
>>> f = Foo()
>>> len(f)
0

저자는 이를 ‘비공식 인터페이스'라고 표현하고 있으며, 이렇게 느슨한 규칙처럼 정의된 사항을 ‘프로토콜’이라고 합니다. 자세하게는 동적 자료형을 제공하는 언어에서 다형성을 제공하는 비공식 인터페이스라고 정의할 수 있습니다.

덕타이핑의 철학에 따라 코드를 작성하시면 좀 더 파이썬스러운 클래스를 정의할 수 있습니다. 자세한 내용은 저의 이전 포스팅을 참고해주세요.

덕타이핑은 파이썬의 유연한 스타일에 아주 잘 어울립니다. 그렇다고 파이썬에 정적인 인터페이스 선언이 없는 것은 아닙니다.

우선 동적인 예를 하나 더 보고, 정적인 인터페이스를 소개하겠습니다.

멍키 패칭

멍키 패칭(monkey patching)은 소스 코드를 건드리지 않고 런타임에 클래스나 모듈을 변경하는 행위를 말합니다.

멍키 패칭을 이용해서 우리는 어떤 클래스를 특정 자료형으로 만들 수 있습니다. 이전 포스트에서 다뤘던 FrenchDeck 클래스를 예로 들어보겠습니다.

이 클래스는 시퀀스형입니다. __iter__() 를 구현하지 않았지만, __getitem__()을 통해 iteration을 할 수 있고, __contains__()를 구현하지 않았지만, __getitem__()을 통해 in 연산자를 사용할 수 있습니다.

이렇게 시퀀스로 동작하는 클래스라면, 랜덤으로 섞는 연산을 직접 구현할 필요 없이, 표준 라이브러리의 random.shuffle() 함수를 사용할 수 있습니다. 다만, 가변 시퀀스만이 random.shuffle() 함수에서 사용할 수 있습니다.

파이썬에서는 이럴 때, 런타임에 클래스에 메서드를 추가해서 인터페이스를 충족시킬 수 있습니다.

>>> def set_card(deck, position, card):
...     deck._cards[position] = card
...
>>> FrenchDeck.__setitem__ = set_card
>>> shuffle(deck)

ABC의 정적인 인터페이스 선언

지금까지는 파이썬에서 덕타이핑에 따라 매우 동적으로 인터페이스를 따르는 방법을 설명했습니다. 이제 파이썬에서 정적으로 인터페이스를 정의하고 따르게 만드는 방법을 설명 드리겠습니다.

ABC(Abstract Base Class)는 추상 베이스 클래스로, 스스로 인스턴스를 만들 수 없지만, 자식 클래스에게 특정 인터페이스를 강제할 수 있습니다. C++이나 JAVA에서 인터페이스를 사용하는 방식과 유사합니다.

아래는 ABC를 사용한 예입니다.

위 예에서, ABC 중 하나인 collections.MutableSequence를 상속하여, 가변 시퀀스형임을 명시적으로 표현할 수 있습니다.

위 방식의 장점은 매우 명확합니다. 1. FrenchDeck2 클래스가 가변 시퀀스임을 쉽게 알 수 있다. 2. collections.MutableSequence의 11개의 메서드를 상속하여 바로 사용할 수 있다.

다만 파이썬스럽지 못한 단점이 있습니다. 필요하지 않은 추상 메서드(__delitem__(), insert())도 모두 구현해야 한다는 점입니다.

저자는 사용자가 확장할 수 있는 프레임워크를 만들지 않는 한 직접 ABC를 만들지 않아야 한다고 충고합니다. 유연성이 떨어지고 코드가 복잡해지기 때문입니다.

이렇게 딱딱하고 고정적인 방식은 사실 파이썬의 철학에 맞지 않습니다.

구스 타이핑(Goose Typing)

구스 타이핑은 조금 거칠게 말하자면 ABC를 동적이고 유연하게 사용하는 방법 중 하나라고 볼 수 있습니다.

구스 타이핑(goose typing): “어떤 객체 a에 대해 isinstance(a, cls) 가 True 이면 a 는 클래스 cls 의 객체다. 또는 어떤 클래스 sub_cls에 대해 issubclass(sub_cls, cls) 가 True 이면 sub_cls는 클래스 cls 의 하위클래스다.” 라는 명제를 기반으로 어떤 객체의 타입을 정의하는 방법.

즉, 어떤 클래스가 isinstance나 insubclass 메소드를 통해서 어떤 자료형인이 구분할 수 있다면, 그 클래스는 해당 자료형으로 여기는 것입니다. ABC를 상속하고, 추상 메서드를 모두 구현해야만 하는 방식보다 훨씬 동적이고 유연한 방법입니다.

이렇게 구스 타이핑을 구현하는 방법은 여러가지가 있습니다.

__subclasshook__() 메소드

아래는 __len__() 던더메소드만을 구현한 Struggle 이라는 클래스의 예입니다.

>>> class Struggle:
...     def __len__(self): return 1
...
>>> from collections import abc
>>> isinstance(Struggle(), abc.Sized)
True

collections.abc.Sized 를 상속하지 않았지만, isinstance를 실행하면 Struggle 클래스의 인스턴스를 abc.Sized 자료형으로 인식합니다. 이는 abc.Sized 내부에서 __subclasshook__() 던더메서드를 이용해 __len__() 던더메서드를 구현한 인스턴스는 모두 서브클래스로 인식하도록 정의했기 때문입니다.

class Sized(metaclass=ABCMeta):
    ==== 중간 생략 ====
    @classmethod
    def __subclasshook__(cls, C):
        is cls is Sized:
            if any('__len__' in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented  # __len__이 없으면 서브클래스 검사를 진행하도록

위처럼 abc.Sized 클래스 내부에 __subclasshook__() 던더메서드를 정의하여, __len__() 메서드를 구현한 모든 클래스는 abc.Sized 자료형으로 인식하도록 할 수 있습니다.

register() 함수

register() 함수를 이용하면, 클래스를 특정 abc의 서브클래스로 등록할 수 있습니다.

collections.abc의 소스 코드에서는 register() 함수를 이용해서 각종 시퀀스 클래스들을 Sequence의 서브클래스로 등록합니다.

Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)

또는 register 데커레이터를 이용할 수도 있습니다. 아래는 책에서 저자가 제시하는 TomboList라는 예제입니다.

TomboList에서는 register 데커레이터를 이용해서 Tombola의 서브클래스임을 등록하고 있습니다. 또 동시에 표준 라이브러리 list를 상속해서 list의 기능들을 이용합니다.

마무리

파이썬에서의 인터페이스 정의와, 이를 좀 더 파이썬스럽게 해결하기 위한 방법인 구스 타이핑에 대해서 알아봤습니다.

사용자가 확장할 수 있는 프레임워크를 만들지 않는 한 우리가 직접 ABC를 만들지 않아야 한다. 직접 ABC를 만들고 싶다면, 일단 먼저 덕 타이핑을 이용해서 문제를 해결해보라.

라고 권고하고 있습니다. ‘단순함이 복잡함보다 낫다’는 파이썬의 철학을 강조하면서 마무리하겠습니다.

즐거운 코딩 되세요~ 감사합니다.

이 포스트는 루시아누 하말류의 책 [Fluent Python]을 참고해서 작성했습니다.

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/