#!/usr/bin/env python # coding: utf-8 # # 18. \_\_missing\_\_ 을 사용해 키에 따라 다른 디폴트 값을 생성하는 방법을 알아두라 # setdefault와 defaultdict 모두 사용하기가 적당하지 않은 경우가 있다. # In[1]: pictures = {} path = 'profile_1234.png' # In[2]: if (handle := pictures.get(path)) is None: try: handle = open(path, 'a+b') except OSError: print(f'경로를 열 수 없습니다: {path}') raise else: pictures[path] = handle # In[3]: handle.seek(0) image_data = handle.read() # setdefault를 활용하는 방법도 있다 # In[4]: try: handle = pictures.setdefault(path, open(path, 'a+b')) except OSError: print(f'경로를 열 수 없습니다: {path}') raise else: handle.seek(0) image_data = handle.read() # 이 코드는 문제가 많다. # # 파일 핸들을 만드는 내장 함수인 open이 딕셔너리에 경로가 있는지 여부와 관계없이 항상 호출된다. # # 내부 상태를 관리하려 한다면 프로필 사진의 상태를 관리하기 위해 defaultdict을 쓸 수 있다고 가정할수도 있다. # In[5]: from collections import defaultdict # In[6]: def open_picture(profile_path): try: return open(profile_path, 'a+b') except OSError: print(f'경로를 열 수 없습니다: {profile_path}') raise # In[7]: pictures = defaultdict(open_picture) handle = pictures[path] handle.seek(0) image_data = handle.read() # 문제는 defaultdict 생성자에 전달한 함수는 인자를 받을수 없다는데 있다. # # 이는 defaultdict이 호출하는 도우미 함수가 처리 중인 키를 알 수 없다는 뜻이다. # # 이로 인해 파일 경로를 사용해 open을 호출할 방법이 없다. # # 이런 상황에서는 setdefault와 defaultdict 모두 필요한 기능을 제공하지 못한다. # In[8]: class Pictures(dict): def __missing__(self, key): value = open_picture(key) self[key] = value return value # In[9]: pictures = Pictures() handle = pictures[path] handle.seek(0) image_data = handle.read() # dict 타입의 하위 클래스를 만들고 \_\_missing\_\_ 특별 메서드를 구현하면 키가 없는 경우를 처리하는 로직을 커스텀화 할 수 있다. # In[10]: pictures # ## 기억해야 할 내용 # - 디폴트 값을 만드는 계산 비용이 높거나 만드는 과정에서 예외가 발생할 수 있는 상황에서는 dict의 setdefault 메서드를 사용하지마라 # - defaultdict에 전달되는 함수는 인자를 받지 않는다. 따라서 접근에 사용한 키 값에 맞는 디폴트 값을 생성하는 것은 불가능하다. # - 디폴트 키를 만들 때 어떤 키를 사용했는지 반드시 알아야 하는 상황이라면 직접 dict의 하위클래스와 \_\_missing\_\_ 메서드를 정의하면 된다. # In[12]: defaultdict(open("aa.txt", 'a'))