11.1. 파이썬 문화에서의 인터페이스와 프로토콜
- 인터페이스: 시스템에서 어떤 역할을 할 수 있게 해주는 객체의 공개 메서드의 일부
- 프로토콜: 어떤 역할을 완수하기 위한 메서드 집합으로서의 인터페이스
11.2. 파이썬은 시퀀스를 찾아낸다
- 파이썬은 __gititem__() 메서드만 구현하더라도 __gititem__() 메서드를 호출해서 객체를 반복하고 in 연산자를 사용할 수 있게 해준다.
11.3. 런타임에 프로토콜을 구현하는 멍키 패칭
import collections
from random import shuffle
Card = collections.namedtuple("Card", ["rank", "suit"])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "spades diamonds clubs hearts".split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
def set_card(deck, position, card):
deck._cards[position] = card
FrenchDeck.__setitem__ = set_card # set_card 함수를 FrenchDeck 클래스의 __setitem__이라는 이름의 속성에 할당한다.
deck = FrenchDeck()
shuffle(deck) # 이제 FrenchDeck 클래스가 가변 시퀀스 프로토콜에 필요한 메서드를 구현하므로, deck을 섞을 수 있다.
deck[:5]
- deck 객체에 _cards라는 이름의 속성이 있고, _cards가 가변 시퀀스임을 set_card()가 알고 있다는 것이 비결.
- set_card() 함수는 FrenchDeck 클래스의 __setitem__ 특별 메서드에 연결된다. 이 방법은 멍키 패칭(monkey patching)의 한 예이다.
- 멍키 패칭: 소스코드를 건드리지 않고 런타임에 클래스나 모듈을 변경하는 행위
11.4. 알렉스 마르텔리의 물새
- 덕타이핑은 객체의 실제 자료형은 무시하고, 대신 객체가 용도에 맞는 메서드 이름, 시그너처, 의미를 구현하도록 보장하는 데 주안점을 둔다.
- 저자가 제안하는 구스 타이핑: cls가 추상 베이스 클래스인 경우, 즉 cls의 메타클래스가 abc.ABCMeta인 경우에는 isinstance(obj, cls)를 쓴다.
class Struggle:
def __len__(self): return 23
from collections import abc
isinstance(Struggle(), abc.Sized)
# True
- abc.Sized 클래스는 Struggle을 일종의 '서브클래스'로 인식한다.
- numbers, collections.abc 등의 ABC가 표현하는 개념을 실현하는 클래스를 구현할 때는 언제나 해당 ABC를 상속하거나 해당 ABC에 등록하라.
11.5. ABC 상속하기
import collections
Card = collections.namedtuple("Card", ["rank", "suit"])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list("JQKA")
suits = "spades diamonds clubs hearts".split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
def __setitem__(self, position, value):
self._cards[position] = value
def __delitem__(self, position):
del self._cards[position]
def insert(self, position, value):
self._cards.insert(position, value)
- 파이썬은 모듈을 로딩하거나 컴파일할 때가 아니라, 실행 도중 실제로 FrenchDeck2 객체를 생성할 때 추상 메서드의 구현 여부를 확인한다.
11.6. 표준 라이브러리의 ABC
11.6.1. collections.abc의 ABC
collections.abc에 정의된 ABC 주요 속성
- Iterabble, Container, Sized: Iterale은 __iter__()를 통해 반복을, Container는 __contains__()를 통해 in 연산자를, Sized는 __len__()을 통해 len() 메서드를 지원한다.
- Sequence, Mapping, Set: 주요 불변 컬렉션형. 각각 가변형 서브클래스가 있다.
- MappingView: items(), keys(), values() 메서드에서 반환된 객체는 각각 ItemView, KeysView, ValuesView를 상속한다.
- Callable, Hashable: 주로 어떤 객체를 호출하거나 해시할 수 있는지 안전하게 판단하기 위해 isinstance() 함수와 함께 사용된다.
- Iterator: Iterable을 상속한다.
11.6.2. ABC의 숫자탑
- numbers 패키지는 '숫자탑'을 정의한다. Number가 최상위 슈퍼클래스이며, 그 다음으로 Complex, Real, Rational, Integral 순으로 내려간다.
11.7. ABC의 정의와 사용
- 상황 가정: 광고를 무작위순으로 보여주되, 목록에 들어있는 광고를 모두 보여주기 전까지는 같은 광고를 반복하지 않는다.
import abc
class Tombola(abc.ABC): # ABC 상속
@ abc.abstractmethod
def load(self, iterable): # 데커레이터
"""iterable 객체에 있는 항목들을 추가"""
@ abc.abstractmethod
def pick(self):
"""무작위로 항목을 하나 제거하고 반환.
객체가 비어있을 때 이 메서드를 실행하면 `LookupError` 발생"""
def loaded(self):
"""최소 1개의 항복이 있으면 True, 그렇지 않으면 False 반환"""
return bool(self.inspect()) # ABC의 구상 메서드는 반드시 ABC에 정의된 인터페이스(즉, ABC읟 ㅏ른 구상 메서드나 추상 메서드, 혹은 프로퍼티)만 사용해야 한다.
def inspect(self):
"""현재 안에 있는 항목들로 구성된 정렬된 튜플 반환"""
items = []
while True:
try:
items.append(self.pick())
except LookupError:
break
self.load(items) #
return tuple(sorted(items))
11.7.1. ABC 상세 구문
- ABC를 선언할 때는 abc.ABC나 다른 ABC를 상속하는 방법이 가장 좋다.
11.7.2. Tombola ABC 상속하기
- Tombola ABC의 인터페이스를 만족시키는 구상 서브클래스를 만들어보자.
import random
class BingoCage(Tombola): # Tombola 상속
def __init__(self, items):
self._randomizer = random.SystemRandom()
self._items = []
self.load(items)
def load(self, items):
self._items.extend(items)
self._randomizer.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
self.pick()
import random
class LotteryBlower(Tombola):
def __init__(self, iterable):
self._balls = list(iterable)
def load(self, iterable):
self._balls.extend(iterable)
def pick(self):
try:
position = random.randrange(len(self._balls))
except ValueError:
raise LookupError('pick from empty LotteryBlower')
return self._balls.pop(position) # 무작위로 선택된 항목 꺼내기
def loaded(self): # inspect()를 호출하지 않도록 loaded() 메서드를 오버라이드한다.
return bool(self._balls)
def inspect(self):
return tuple(sorted(self._balls)) # inspect() 오버라이드
11.7.3. Tombola의 가상 서브클래스
- 구스 타이핑의 본질적인 기능은 어떤 클래스가 ABC를 상속하지 않더라도 그 클래스의 가상 서브클래스로 등록할 수 있다는 것이다.
- Tombola의 가상 서브클래스 TomboList
from random import randrange
@Tombola.register # TomboList를 Tombola의 가상 서브클래스로 등록한다.
class TomboList(list): # list를 상속한다.
def pick(self):
if self: # list에서 __bool__이 상속된다. 리스트가 비어있지 않으면 True를 반환한다.
position = randrange(len(self))
return self.pop(position) # pick() 메서드는 무작위 인덱스를 전달해서 list에서 상속한 self.pop()을 호출한다.
else:
raise LookupError('pop from empty TomboList')
load = list.extend # list.extend() 메서드를 TomboList.load에 할당한다.
def loaded(self):
return bool(self) # loaded() 메서드를 bool() 함수에 위임한다.
def inspect(self):
return tuple(sorted(self))
Tombola.register(TomboList)
- TomboList를 Tombola 클래스의 가상 서브클래스로 등록했기 때문에 이제 issubclass()와 isinstance() 함수는 TomboList가 Tombola의 서브클래스인 것처럼 판단한다.
11.8. Tombola 서브클래스 테스트 방법
- __subclasses__(): 클래스의 바로 아래 서브클래스의 리스트를 반환하는 메서드. 리스트에 가상 서브클래스는 들어가지 않는다.
- _abc_registry: ABC에서만 사용할 수 있는 데이터 속성으로, 추상 클래스의 등록된 가상 서브클래스에 대한 약한 참조를 담고 있는 WeakSet이다.
'Python' 카테고리의 다른 글
[전문가를 위한 파이썬] 13장 연산자 오버로딩: 제대로 하기 (2) | 2024.09.08 |
---|---|
[전문가를 위한 파이썬] 12장 내장 자료형 상속과 다중 상속 (1) | 2024.08.31 |
[전문가를 위한 파이썬] 10장 시퀀스 해킹, 해시, 슬라이스 (0) | 2024.07.21 |
[전문가를 위한 파이썬] 9장 파이썬스러운 객체 내용 정리 (0) | 2024.06.22 |
[전문가를 위한 파이썬] 8장 객체 참조, 가변성, 재활용 내용 정리 (0) | 2024.06.18 |