들어가며
파이썬의 특출난 장점 중 하나로 간결한 문법이 있다. 이를 활용해 많은 로직을 표현식 한 줄로 작성할 수 있는데, 예를 들어 URL에서 쿼리 문자열(질의 문자열)을 구문 분석해야 한다고 가정하자. 여기서 각 질의 문자열 파라미터는 정수 값을 표현한다. (urllib를 다뤄보지 않았어도 대충 문법만 참고해보자.)
다음 예에서 각 쿼리 문자열 파라미터는 정수 값을 표현한다.
from urllib.parse import parse_qs
my_values = parse_qs('red=5&blue=0&green=', keep_blank_values=True)
print(repr(my_values))
# repr 함수는 https://wikidocs.net/134994를 참고하자.
>>>
{'red': ['5'], 'blue': ['0'], 'green': ['']}
여기서 parse_qs()함수의 인자에 따라 값이 여러 개 존재할 수도 있고 한 개만 있을 수도 있으며, 값이 비어있을 수도 있다. 딕셔너리에 get 메서드를 사용하면 각 상황에 따라 다른 값을 반환할 것이다.
print('Red ',my_values.get('red'))
print('Green ',my_values.get('green'))
print('Opacity ',my_values.get('opacity'))
>>>
Red ['5']
Green ['']
Opacity None
파라미터가 없거나 비어 있으면 기본값으로 0을 할당하게 하면 좋을 것이다. 그러나 이 로직에 if문이나 헬퍼 함수를 쓸 필요를 느끼지 못하고 작업을 bool 표현식으로 처리할 수도 있다.
제대로 파헤쳐보자
파이썬의 문법 덕분에 bool 표현식으로도 쉽게 처리할 수 있다. 이때 사용하는 트릭은 빈 문자열, 빈 리스트, 0이 모두 임시적으로 False로 평가된다는 것이다. 따라서 다음 표현식들의 결과는 첫 번째 서브 표현식이 False일 때 'or' 연산자 뒤에 오는 서브 표현식을 평가한 값이 된다.
# 쿼리 문자열 : 'red=5&blue=0&green='
red = my_values.get('red', [''])[0] or 0
green = my_values.get('green', [''])[0] or 0
opacity = my_values.get('opacity', [''])[0] or 0
print(f'Red :{red!r}')
print(f'Green :{green!r}')
print(f'Opacity :{opacity!r}')
>>>
Red :'5'
Green :0
Opacity :0
red의 경우는 키가 my_values 안에 있다. 값은 '5'만 있는 리스트이다. 암시적으로 True가 되므로 red는 or 표현식의 첫 번째 부분을 할당받는다.
green의 경우는 키는 있으나 값이 존재하지 않는다. 빈 문자열은 암시적으로 False이므로 or 표현식의 결과는 0이 된다.
opacity의 경우 키 자체가 존재하지 않는다. get 메서드는 딕셔너리에 키가 없으면 두 번째 인수를 반환한다. 이 때 기본값이 ['']이므로 green과 같은 동작을 한다.
좀더 자세하게?
위 코드에서 green을 파헤쳐보자. 아마 red와 opacity는 바로 이해가 될 것이다.
green = my_values.get('green', [''])
>> green : ['']
green = my_values.get('green', [''])[0]
>> green : ''
green = my_values.get('green', [''])[0] or 0
>> green : 0 # 위에서 빈 문자열은 False로 처리한다 하였으므로
열심히 표현식을 작성해보았지만 이 표현식은 읽기 어려울 뿐만 아니라 필요한 작업을 모두 수행하지도 못한다. 모든 파라미터 값이 정수가 되게 해서 수학식에서도 값들을 사용할 수 있게 하고 싶은데, 그러려면 각 표현식을 내장 함수 int로 처리해 문자열을 정수 값으로 파싱해야 한다.
red = int(my_values.get('red', [''])[0] or 0)
이 코드는 무척 읽기가 어렵다. 시각적 방해 요소도 너무 많을 뿐만 아니라 사용하기도 쉽지 않고 코드를 구상하는 아이디어도 떠올리기 쉽지 않다. 또한 코드를 처음 읽는 사람은 이 코드가 실제로 어떤 일을 하는지 알아내려고 각 부분을 떼어내 해석하느라 많은 시간을 할애할 수도 있다. 문장이 짧아서 좋은 경우도 많지만 그게 과도하면 오히려 독이 된다.
그렇다면 어떻게 해야 할까?
여기서 if/else 조건식을 사용하면 코드를 짧게 만들면서도 더 명확하게 표현할 수 있다.
red = my_values.get('red', [''])
red = int(red[0]) if red[0] else 0
위의 코드가 훨씬 해석하기가 쉽다. 그러나 이 코드도 여러 줄에 걸친 if/else문을 대체할 정도로 명확하지는 않다.
헌데 다음처럼 모든 로직을 펼쳐서 보면 반복하면 더 복잡해 보인다.
green = my_values.get('green', [''])
if green[0]:
green = int(green[0])
else:
green = 0
헬퍼 함수
이러한 로직을 반복해서 사용해야 할 때 헬퍼 함수를 만드는 것이 좋다.
def get_first_int(values, key, default=0):
found = values.get(key, [''])
if found[0]:
found = int(found[0])
else:
found = default
return found
위의 헬퍼 함수를 쓰면 or을 사용하면 복잡한 표현식이나 if/else 조건식을 활용한 두 줄짜리 버전을 쓸 때보다 호출 코드가 훨씬 더 명확해진다.
green = get_first_int(my_values, 'green')
표현식이 복잡해지기 시작하면 최대한 빨리 해당 표현식을 작은 조각으로 분할하고, 로직을 헬퍼 함수로 옮기는 방안을 고려해야 한다. 무조건 짧은 코드를 만들기보다는 가독성을 선택하는 편이 훨씬 낫다. 이렇게 이해하기 어려운 복잡한 표현식에는 파이썬의 함축적인 문법을 사용하면 안 된다.
참고
- 파이썬 코딩의 기술 - 똑똑하게 코딩하는 법, 브렛 슬라킨 저/오현석 역
'◎ Python > 코딩의 기술 (책)' 카테고리의 다른 글
[코딩의 기술] 7. range보다는 enumerate를 사용하라 (0) | 2023.04.30 |
---|---|
[코딩의 기술] 6. 인덱스보다는 대입을 사용해 데이터를 언패킹하자 (1) | 2022.11.30 |
[코딩의 기술] 4. f-문자열을 통한 인터폴레이션을 사용하라 (1) | 2022.11.28 |
[코딩의 기술] 3. bytes와 str의 차이를 알자 (0) | 2022.11.27 |
[코딩의 기술] 2. PEP 8 스타일 가이드를 따르자 (2) | 2022.09.15 |
자기계발 블로그