[파이썬에서 살아남는법 제 17장] __missing__ 을 사용해 키에 따라 다른 디폴트 값을 생성하는 방법을 알아두라

2023. 3. 2. 12:46카테고리 없음

※본 내용은 파이썬의 코딩 기술 (개정 2판)을 바탕으로 작성 했습니다.

 

내장 dict 타입의 setdefault 메스드는 키가 없는 경우를 짧은 코드로 처리하게 해준다. 

하지만 setdefault  와 defaultdict 모두 사용이 적당하지 않는 경우도 있다. 

아래 예시는 SNS 프로필 사진을 관리하는 프로그램을 작성 한다고 한다. 필요할대 파일을 읽고 쓰기위해  사진의 경로와 핸들을 연관시켜주는 딕셔너리이다. 

pictures = {}
path = 'profile_1234.png'

if (handle := pictures.get(path)) is None:
	try:
		handle = open(path, 'a+b')
	except OSError:
		print(f'Failed to open path {path}')
		raise
	else:
		pictures[path] = handle

handle.seek(0)
image_data = handle.read()

위의 방법은 get  메서드를 통해 딕셔너리를 읽고 else 절에서 핸들을 딕셔너리에 대입한다. 

그럼 이전에 학습했던것과 같이 setdefault 와 defaultdict 를 사용한 코드를 보자. 

try:
	handle = pictures.setdefault(path, open(path, 'a+b'))
except OSError:
	print(f'Failed to open path {path}')
	raise
else:
	handle.seek(0)
	image_data = handle.read()

이코드는 open이 딕셔너리에 경로가 있는지 여부와 상관없이 항상 호출된다. 이로인해 같은 프로그램상에 존재하던 열린 파일 핸들과 혼동될수 있는 새로운 파일 핸들이 생기는 문제가 있을수 있다. 

from collections import defaultdict
def open_picture(profile_path):
	try:
		return open(profile_path, 'a+b')
	except OSError:
		print(f'Failed to open path {profile_path}')
		raise

pictures = defaultdict(open_picture)
handle = pictures[path]
handle.seek(0)
image_data = handle.read()
>>>
Traceback ...
TypeError: open_picture() missing 1 required positional
argument: 'profile_path'

이번에는 defaultdict 생성자에서 전달한 함수의 인자를 받을수 없다. 

따라서 setdefault 와 defaultdict 를 사용한 코드보다는 다른 방법이 필요하다. 

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()

Pictures[path] 딕셔너리 접근에서 path 가 딕셔너리에 없으면 __missing__ 메서드가 호출 된다.

이코드는 메서드에 해당하는 디폴트 값을 생성해 딕셔너리에 넣은다음 딕셔너리에 같은 경로에 접근 하면 이미 해당 원소가 딕셔너리에 들어 있음으로 __missing__ 메서드가 호출되지 않는다.