__missing__
method 를 사용해, 파이썬 dictionary 를 상속한 클래스에 대해서 key가 없을 때 default값을 설정하는 법을 알아보자
key 값이 dict 에 없을 때 처리하는 여러 방법이 있다.
pictures = {}
path = 'profile_1234.png'
if (handle := pictures.get(path)) is None:
try:
handle = open(path, 'a+b')
except OSError:
print(f'경로를 열 수 없습니다: {path}')
raise
else:
pictures[path] = handle
handle.seek(0)
image_data = handle.read()
이는 딕셔너리의 get method 를 이용해 해당 key가 존재하는지 확인하고, 존재하지 않으면 딕셔너리에 따로 할당해준 다음 프로필 이미지를 읽어오는 코드이다.
try:
handle = pictures.setdefault(path, open(path, 'a+b'))
except OSError:
print(f'경로를 열 수 없습니다: {path}')
raise
else:
handle.seek(0)
image_data = handle.read()
이 코드는 file handle을 만드는 내장 함수인 open
이 항상 실행된다. 이로 인해 같은 프로그램 상에 존재하던 열린 file handle과 혼동될 수 있는 새로운 file handle이 생길 수 있다.
또한 open
, setdefault
두 군데에서 오류가 발생할 가능성이 있기 때문에 이를 구분하지 못하는 경우가 생길 수 있다.(같은 책의 "better way 43. 커스텀 컨테이너 타입은 collections.abc 를 상속하라" 참고)
from collections import defaultdict
def open_picture(profile_path):
try:
return open(profile_path, 'a+b')
except OSError:
print(f'경로를 열 수 없습니다: {profile_path}')
raise
# 오류가 나는 부분.
pictures = defaultdict(open_picture)
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
collections
모듈의 defaultdict
클래스를 활용하는 방법이다.
이 방법은 문제가 있는데, defaultdict 생성자에 전달한 함수(여기선 open_picture
)는 인자를 받을 수 없다. 따라서 무슨 키를 취급하는지 알 수가 없다.
path = 'profile_1234.png'
class Pictures(dict):
def __missing__(self, key):
value = open_picture(key)
self[key] = value
return value
pictures = Pictures()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
동작은 직관적이다. picures
클래스는 dict을 상속한 클래스이고, 말 그대로 존재하지 않는(__missing__
)key를 호출하면 __missing__
method가 호출된다. 동작을 한번 확인해 보자.
print(handle)
# <_io.BufferedRandom name='profile_1234.png'>
리턴값을 없애면 어떻게 되는지 확인해보자
path = 'profile_1234.png'
class Pictures(dict):
def __missing__(self, key):
value = open_picture(key)
self[key] = value
# return value
pictures = Pictures()
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
print(handle)
논타입 에러가 발생하고, 에러가 발생하기 전 지점에 handle
을 확인해 보면
예상대로 __mising__의 리턴값이 None인 것을 확인할 수 있다.