민주의네모들

[python] List Comprehension과 Generator Expression 본문

Algorithm Study

[python] List Comprehension과 Generator Expression

mjoooo 2020. 1. 30. 15:13
반응형

 

'본 포스팅은 글쓴이 개인의 공부 목적이므로, 틀린 부분이 있다면 댓글로 달아주시면 감사하겠습니다.'

1. List Comprehension이란?

 

파이썬은 "list comprehension"이라는 개념을 지원한다.

이것은 쉽게 말해서, list를 쉽고 빠르게 만들 수 있는 방법이다.

 

중고등학교 수학시간에 {w | w ⊂ V & P(w)} 대충 이런 식을 본 적이 있는가?

이것은 '집합 V에 속하는 원소 w중에, 성질 P를 만족하는 모든 w들의 집합'이라고 말할 수 있다.

 

이 수식을 list comprehension으로 나타내면 어떨까?

[w for w in V if P(w)] 가 된다.

 

 

아직 직관적으로 이해가 되지 않는다면, 예시를 살펴보자.

 

1부터 10까지의 숫자의 제곱 중 홀수 만을 추출하는 것을 코드로 짜 보자.

고차원 함수를 이용하면,

#고차원 함수 이용

numbers=[1,2,3,4,5,6,7,8,9,10]
x=map(lambda n:n*n, filter(lambda n:n%2==1, numbers))
x   #[1, 9, 25, 49, 81]

이렇게 짤 수 있을 것이다.

똑같은 내용을 List comprehension을 사용하면,

#List Comprehension 이용

x=[n*n for n in numbers if n%2==1]
x    #[1, 9, 25 49, 81]

이렇게 짤 수 있다.

 

즉, list comprehension은 for in if 를 적절히 사용하여

코드 한 줄에 list를 빠르게 만들 수 있는 편리하고 강력한 방법이다.

 

#list comprehension 활용 예시

P1=[x**2 for x in range(10)]
P1   #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

P2=[2**i for i in range(13)]
P2   #[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]

P3=[x for x in P1 if x%2==0]
P3   #[0, 4, 16, 36, 64]

 

list comprehension 사용에 익숙해 진다면 리스트 안에 튜플을 넣거나,

그 튜플 중 특정 위치에 있는 원소만 뽑아서 다시 list로 만드는 등 (아래의 예시 참고)

 

리스트를 자유자재로 활용할 수 있어 매우 편리하다.

 

#주어진 myList의 DNA서열들을 길이 별로 정렬하여, 나열해보자.

myList=['TAAccCtAa', 'ccctaa', 'ccCTAAccctaa', 'cccTAAcc']

myTuple=[(len(x), x) for x in myList] #(DNA서열길이, DNA서열) 형식의 튜플을 만든다.
myTuple.sort() #DNA서열길이로 myTuple의 튜플을 정렬한다.
myTuple
#[(6, 'ccctaa'), (8, 'cccTAAcc'), (9, 'TAAccCtAa'), (12, 'ccCTAAccctaa')]

sortedList=[x for (_,x) in myTuple] #정렬된 myTuple에서 DNA서열만 가져와 sortedList라는 새 리스트를 만든다.
sortedList
#['ccctaa', 'cccTAAcc', 'TAAccCtAa', 'ccCTAAccctaa']

 

2.  Generator Expression이란?

 

"generator expression"은 python 2.6. 에서 소개되었다.

 

이것은 list comprehension과 syntax나 작동하는 방법 면에서 비슷하지만, 

list대신 generator를 반환한다는 점에서 크게 다르다고 할 수 있다.

 

제너레이터는 모든 값을 메모리에 담고 있지 않고 그때그때 값을 생성(generator)해서 반환하기 때문에

제너레이터를 사용할 때에는 한 번에 한 개의 값만 순환(iterate)할 수 있다.

 

 

다시말해,

generator expression은 느긋한 계산법(Lazy evaluation)이라고 할 수 있다.

 

컴퓨터 프로그래밍에서 느긋한 계산법(Lazy evaluation)은 계산의 결과값이 필요할 때까지 계산을 늦추는 기법이다.

느긋하게 계산하면 필요 없는 계산을 하지 않으므로 실행을 더 빠르게 할 수 있고,

복합 수식을 계산할 때 오류 상태를 피할 수 있고,

무한 자료 구조를 쓸 수 있고,

미리 정의된 것을 이용하지 않고 보통 함수로 제어 구조를 정의할 수 있다.

 

#generator expression의 예

x=(n*2 for n in range(10))
type(x)
#<class 'generator'>
x
#<generator object <genexpr> at 0x000002AA7DBD7648>

next(x)    #next()로 값을 불러올 수 있다.
#0
next(x)
#2
next(x)
#4

 

 

3. List Comprehension과 Generator Expression의 차이

 

generator expression은 기본적으로 list comprehension과 같은 일을 하지만, 

가장 큰 차이점은 generator expression이 일을 lazily하게 한다는 것이다.

 

표면적인 차이는 괄호의 모양으로 나타난다.

list comprension은 [ ] 를 사용하는 반면, generator expression은 ( ) 를 사용한다. 

 

x=[n*2 for n in range(10000)] #List Comprehension
y=(n*2 for n in range(10000)] #Generator Expression

type(x)
#<type 'list'>

type(y)
#<type 'generator'>

 

이 때, 우리는 list의 특정 원소 위치에 접근할 수 있지만,

generator의 원소에 접근하려고 하면 오류가 뜬다. 

 

x[3]
# 6

y[3]
# Traceback (most recent call last):
#  File "<pyshell#27>", line1, in <module>
#    y[3]
#   TypeError: 'generator' object has no attribute '__getitem__'

 

 

4. Generator Expression을 사용하는 이유

 

generator expression이 lazy evaluation이라는 점에서 대충 눈치 챈 사람도 있을 것이다.

 

generator expression을 사용하는 이유는,

그렇다. 바로 시간메모리 때문이다.

 

generator expression은 미리 모든 값을 계산하지 않고 필요할 때마다 값을 그때그때 계산하기 때문에

수행 시간이 긴 연산을 필요한 순간까지 늦출 수 있고 메모리를 절약할 수 있다.

 

import time

t1=time.clock()
x=[n*2 for n in range(100000000)] #List Comprehension
t2=time.clock()
print(t2-t1)
#15.3619699964

t1=time.clock()
y=(n*2 for n in range(100000000)) #Generator Expression
t2=time.clock()
print(t2-t1)
#25.3919176142

import sys
sys.getsizeof(x)
#40764032

sys.getsizeof(y)
#40
반응형