Effective Python 2일차
Better Way 7
map과 filter 대신 리스트 컴프리헨션을 사용하자
파이썬에선 리스트에서 다른 리스트를 만들어내는 문법이 있다. 이 문법을 사용한 표현식을 리스트 컴프리헨션(list comprehension)이라고함
a = [1,2,3,4]
squares = [x**2 for x in a] # [1, 4, 9, 16]
간단한 연산에서는 list comprehension이 더 명확함 심지어 출력값을 삭제도 가능하다
even_squares = [x**2 for x in a if x % 2 ==0]
# [4, 16]
내장함수 map과 filter를 이용하면 같은 결과를 얻을 수 있지만 읽기 어렵다
alt = map(lambda x: x**2, filter(lambda x: x % 2 ==0, a))
뭔가 있어보이지만 간결하지 않다
결론
- list comprehension 쓰도록 하자.
- 딕셔너리와 set도 사용할 수 있다.
- map과 filter는 멋있지만 자제 하자.
Better Way 8
리스트 컴프리헨션에서 표현식을 두 개 넘게 쓰지 말자
리스트 컴프리헨션에서는 다중 루프와 다중 조건을 지원한다
# 사용 예
matrix = [[1,2,3], [4,5,6], [7,8,9]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
코드가 짧지만 읽기 어렵다. 안어렵다면 다음 코드를 보자
filtered = [[x for x in row if x % 3 ==0]
for row in matrix if sum(row) >= 10]
반복문안에 조건문까지 포함되어있다 복잡하다!!
flat = []
for sublist1 in my_list:
for sublist2 in sublist1:
flat.extend(sublist2)
결론
- 표현식이 두개가 넘어가면 사용을 자제하자.
- 위처럼 코드를 작성하면 길지만 읽기 쉽다.
Better Way 9
컴프리헨션이 클 때는 제너레이터 표현식을 고려하자
리스트 컴프리헨션의 문제점
- 입력 시퀀스에 있는 각 값별로 아이템을 하나씩 담은 새 리스트를 통째로 생성함.
- 입력값이 적을 때는 괜찮지만 클때는 메모리를 많이 소모함.
참고
리스트에 특정 값(a[0]와 같은)을 수정할때는 생성된 리스트에서 직접적으로 값을 수정 하지만, 값을 추가하거나 전체를 수정하면 리스트를 메모리에서 재할당하기에 메모리소모가 커짐.
파이썬은 이러한 문제를 해결하기 위해 제너레이터 표현식(generator expression)을 제공한다.
- 제너레이터 표현식은 실행될 때 출력 시퀀스를 모두 구체화(메모리에 로딩) 하지 않는다.
- 대신 이터레이터로 평가된다. (한번씩 진행된다는 뜻)
사용방법
# list comprehesion
value = [len(x) for x in open('/tmp/my_file.txt')] # [100, 57, 15, 1 ... ]
# generator
it = (len(x) for x in open('/tmp/my_file.txt')) # <generator object <genexpr> at 0x123123123>
print(next(it)) # 100
print(next(it)) # 57
제너레이터의 강점은 또 다른 제너레이터와 사용 가능하다는 것!!!
roots = ((x, x**0.5) for x in it)
print(next(it)) # (15, 3.872983346207417)
제너레이터를 연결하면 파이썬에서 매우 빠르게 실행 할 수 있다. 큰 입력 스트림에 동작하는 기능을 결합하는 방법을 찾을 때는 제너레이터 표현식이 최선의 도구다. 단, 제너레이터 표현식이 반환한 이터레이터에는 상태가 있으므로 이터레이터를 한 번 넘게 사용하지 않도록 주의해야 한다. (Better Way 17에서 다룰 예정)
결론
- 리스트 컴프리헨션은 큰 입력을 처리할때 메모리 소모가 심해서 문제를 일으킬 수 있다.
- 제너레이터는 이터레이터로 한 번에 한번씩 출력 하므로 메모리 문제를 피할 수 있다.
- 제너레이터 표현식은 서로 연결되어 있을 때 짱짱 빠르다.
Better Way 10
range보다는 enumerate를 사용하자
내장 함수 range는 정수 집합을 순회(iterate)하는 루프를 실행할때 유용하다.
flavor_list = ['syntha-6', 'chocolate', 'banana', 'python']
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print('%d: %s' %(i+1, flavor))
# 1: syntha-6
# 2: chocolate ...
range를 사용하여 flavor 리스트를 순차적으로 출려하는 코드인데 이 코드는 리스트의 길이를 알아내야 하고, 배열을 인덱스로 접근해야하며, 읽기 불편하다
이런 상황에서는 내장 함수 enumerate를 사용하자!!!
enumerate는 lazy generator로 이터레이터를 감싼다.
# enumerate 사용 예
for i, flavor in enumerate(flavor_list):
print('%d: %s' %(i+1, flavor)) # 출력 값은 위의 코드와 같음
# enumerate로 세기 시작할 숫자를 지정할 수 있다.
for i, flavor in enumerate(flavor_list, 1):
print('%d: %s' %(i, flavor)) # 출력 값은 위의 코드와 같음
결론
- enumerate는 이터레이터를 순회하면서 이터레이터에서 각 아이템의 인덱스를 얻어오는 간결한 문법을 제공한다.
- range로 루프를 실행하고 인덱스로 접근하기 보다는 enumerate를 사용하자.
- enumerate에 두 번째 파라미터를 사용하면 세기 시작할 숫자를 지정할 수 있다. (기본값은 0)