__missing__

한재민·2023년 3월 1일
0

effective python

목록 보기
2/2
post-custom-banner

목적

__missing__ method 를 사용해, 파이썬 dictionary 를 상속한 클래스에 대해서 key가 없을 때 default값을 설정하는 법을 알아보자


key 값이 dict 에 없을 때 처리하는 여러 방법이 있다.

예시 상황

  • 파일 시스템에 있는 SNS프로필 사진을 관리하는 프로그램을 작성하는 상황
  • 필요할 때 파일을 읽고 쓰기 위해 프로필 사진의 경로, 열린 파일 핸들을 연관시키는 딕셔너리가 필요

1. get 활용

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가 존재하는지 확인하고, 존재하지 않으면 딕셔너리에 따로 할당해준 다음 프로필 이미지를 읽어오는 코드이다.

2. setdefault 활용

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 를 상속하라" 참고)

3. defaultdict 활용

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)는 인자를 받을 수 없다. 따라서 무슨 키를 취급하는지 알 수가 없다.

4. __missing__ 사용

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인 것을 확인할 수 있다.

profile
열심히 하는 사람
post-custom-banner

0개의 댓글