Effective Python 3일차
2017-02-20
Better Way 11
이터레이터를 병렬로 처리하려면 zip을 사용하자
리스트 컴프리헨션(list comprehension)을 사용하면 소스 리스트(source list)에 표현식을 적용하여 파생 리스트(derived list)를 쉽게 얻을 수 있다.
파생 리스트와 소스 리스트는 서로의 인덱스로 연관되어있다. 두 리스트를 병렬로 순회하려면 소스 리스트인 names의 길이만큼 순회하면 된다.
names = [ 'Cecilia', 'Lise', 'Marie']
letters = [ len(n) for n in names ]
longest_name = None
max_letters = 0
for i in range(len(names)):
count = letters[i]
if count > max_letters:
longest_name = names[i]
max_letters = count
# names와 letters를 인덱스로 접근하면 코드 읽기가 어려워짐
# 루프의 인덱스 i로 배열에 접근하는 동작이 두 번 일어남
Better Way 10 처럼 enumerate를 사용하면 이런 문제점을 약간 개선할 수 있지만 완벽하지는 않다.
for i, name in enumerate(names):
count = letters[i]
if count > max_letters:
longest_name = name
max_letters = count
# 뭔가 아쉽다
파이썬3에서 zip은 지연 제너레이터롤 이터레이터 두 개 이상을 감싼다. zip 제너레이터는 각 이터레이터부터 다음 값을 담은 튜플을 얻어온다. zip 제너레이터를 사용한 코드는 다중 리스트에서 인덱스로 접근하는 코드보다 훨씬 명료하다
for name, count in zip(names, letters):
if count > max_letters:
longest_name = name
max_letters = count
# 깔끔!
내장함수 zip의 두가지 문제
- 파이썬2에서의 zip은 제너레이터가 아님.
- 제공한 이터레이터를 모두 순회해서 zip으로 만든 모든 튜플을 반환한다
- 이 과정에서 메모리를 많이 사용하여 프로그램이 망가지는 원인이 되기도 함.
- 입력 이터레이터들의 길이가 다르면 zip이 이상하게 동작한다.
- name 리스트에 다른 이름을 추가 했지만 letters의 카운터를 업데이트 하지 않는다면 예상치 못한 결과가 나오게 된다.
names.append('Rosalind') for name, count in zip(names, letters): print(name) # Cecilia # Lise # Marie
‘Rosalind’가 결과에 없다!!!!
- name 리스트에 다른 이름을 추가 했지만 letters의 카운터를 업데이트 하지 않는다면 예상치 못한 결과가 나오게 된다.
결론
- 내장 함수 zip은 이터레이터를 병렬로 순회할 때 사용할 수 있다.
- 길이가 다른 이터레이터를 사용하면 zip은 그 결과를 조용히 잘라낸다
- zip으로 실행할 리스트의 길이가 확신할 수 없다면, 대신 내장 모듈 itertools의 zip_longest를 사용해보자
Better Way 12
for와 while 루프 뒤에는 else블록을 쓰지 말자
- if/else문에서 else는 ‘이전 블록이 실행되지 않으면 이 블록이 실행된다’는 의미다.
- try/except 문에서 except도 마찬가지로 ‘이전 블록에서 실패하면 이 블록이 실행된다’고 정의할 수 있다.
- try/except/else의 else도 ‘이전 블록이 실패하지 않으면 실행하라’는 뜻!
for i in range(3):
print('Loop %d' % i)
if i == 1:
break # break문을 사용해야 else 블록을 건너뛸 수 있다.
else:
print('Else block!')
# Loop 0
# Loop 1
빈 시퀀스를 처리하는 루프문에서는 else블록이 즉시 실행된다!
for x in []:
print('Never runs')
else:
print('For Else block!')
# For Else block!
#else블록은 while루프가 처음부터 거짓인 경우에도 실행된다.
while False:
print('Never runs')
else:
print('While Else block!')
# While Else block!
루프 다음에 오는 else 블록은 루프로 뭔가를 검색할 때 유용하다. 예를 들어 두 숫자가 서로소인지를 판별한다면, 모든 공약수를 구하고 모든 옵션을 시도한 후에 루프가 끝난다. else블록은 루프가 break를 만나지 않아서 숫자가 서로소일 때 실행된다.
a = 4
b = 9
for i in ragne(2, min(a, b) + 1):
print('Testing', i)
if a % i == 0 and b % i == 0:
print('Not comprime')
break
else:
print('Coprime')
# Testing 2
# Testing 3
# Testing 4
# Coprime
이런 코드를 작성하지 말고 헬퍼 함수를 사용하는게 좋다
두 가지 스타일의 헬퍼 함수
# 1. 찾으려는 조건을 찾았을 때 바로 반환, 루프가 실패로 끝나면 기본 결과(True)를 반환.
def coprime(a, b):
for i in ragne(2, min(a, b) + 1):
if a % i == 0 and b % i == 0:
return False
return True
# 2. 루프에서 찾으려는 대상을 찾았는지 알려주는 결과 변수를 사용. 뭔가를 찾으면 즉시 break로 루프를 중단.
def coprime2(a, b):
is_comprime = True
for i in ragne(2, min(a, b) + 1):
if a % i == 0 and b % i == 0:
is_comprime = False
return is_comprime
결론
- 파이썬에는 루프문 바로 뒤에 else블록을 사용할 수 있게하는 특별한 문법이 있다.
- 루프 본문이 break문을 만나지 않은 경우에만 루프 다음에 오는 else블록이 실행된다.
- 루프 뒤에 else블록 쓰지마라. 혼동하기쉽고 직관적이지 않다.