생성기 표현 대 목록 이해


411

언제 파이썬에서 생성자 표현식을 사용해야하고리스트 이해를 사용해야합니까?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

27
[exp for x in iter]그냥 설탕이 될 수 list((exp for x in iter))있습니까? 또는 실행 차이가 있습니까?
b0fh

1
그것은 관련 질문이 있다고 생각하므로 yield를 사용할 때 함수에서 생성기 표현식 만 사용할 수 있습니까? 아니면 생성기 객체를 반환하는 함수에 yield를 사용해야합니까?

28
@ b0fh 귀하의 의견에 매우 늦게 답변 : Python2에는 작은 차이가 있으며 루프 변수는 목록 이해에서 누출되지만 생성기 표현식은 누출되지 않습니다. X = [x**2 for x in range(5)]; print x와 비교 Y = list(y**2 for y in range(5)); print y하면 두 번째 오류가 발생합니다. Python3에서 목록 이해는 실제로 list()예상대로 생성 된 표현식에 대한 구문 설탕 이므로 루프 변수가 더 이상 누출되지 않습니다 .
Bas Swinckels

12
PEP 0289를 읽는 것이 좋습니다 . 로 요약 "고성능 등이 PEP를 소개합니다 발생기 표현, 지능형리스트 및 발전기의 메모리를 효율적으로 일반화" . 또한 사용시기에 대한 유용한 예도 있습니다.
icc97

5
@ icc97 나는 또한 파티에 8 년 늦었고 PEP 링크는 완벽했다. 쉽게 찾을 수있게 해주셔서 감사합니다!
eenblam

답변:


283

John의 대답 은 좋습니다 (여러 번 반복해서 반복하려고 할 때 목록 이해력이 더 좋습니다). 그러나 목록 방법 중 하나를 사용하려면 목록을 사용해야한다는 점도 주목할 가치가 있습니다. 예를 들어 다음 코드는 작동하지 않습니다.

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

기본적으로 생성자가 한 번만 반복하면 생성기 표현식을 사용하십시오. 생성 된 결과를 저장하고 사용하려면 목록을 이해하는 것이 좋습니다.

성능이 다른 것을 선택하는 가장 일반적인 이유이므로, 내 조언은 걱정하지 않고 하나만 선택하는 것입니다. 프로그램이 너무 느리게 실행되고있는 경우에만 돌아가서 코드 튜닝에 대해 걱정해야합니다.


70
때때로 당신은 예를 들어, 당신이 협력 스케줄링 수율을 사용하여와 코 루틴을 작성하는 경우 - 발전기를 사용합니다. 그러나 당신이 그렇게하고 있다면, 아마도이 질문을하지 않을 것입니다;)
ephemient

12
나는 이것이 오래되었다는 것을 알고 있지만 생성기 (및 모든 반복 가능)를 확장 목록으로 추가 할 수 있다는 점에 주목할 가치가 있다고 생각합니다 a = [1, 2, 3] b = [4, 5, 6] a.extend(b). (당신은 주석에 줄 바꿈 ?? 추가 할 수 있습니다)
jarvisteve

12
@jarvisteve 당신의 예는 당신이 말하는 단어에 달려 있습니다. 여기에도 좋은 점이 있습니다. 생성기를 사용하여 목록을 확장 할 수는 있지만 목록을 생성기로 만들 필요는 없습니다. 생성기는 목록으로 확장 할 수 없으며 생성기는 반복 가능하지 않습니다. a = (x for x in range(0,10)), b = [1,2,3]예를 들어. a.extend(b)예외가 발생합니다. b.extend(a)를 모두 평가합니다.이 경우 처음에는 발전기로 만들 필요가 없습니다.
Slater Victoroff

4
@ SlaterTyranus 당신은 100 % 정확하고 정확성을 위해 당신을 찬성했습니다. 그럼에도 불구하고, 그의 의견은 OP의 질문에 대한 답이 아닌 유용한 답변이라고 생각합니다. 왜냐하면 그들은 여기에 자신을 찾는 사람들이 검색 목록에 '목록 이해와 결합 생성기'와 같은 것을 입력했기 때문에 도움이 될 것이기 때문입니다.
rbp 2015 년

1
생성기를 사용하여 한 번 반복하는 이유 (예 : 메모리 부족에 대한 관심이 한 번에 하나씩 "페치"값에 대한 관심을 무시 함 )는 여러 번 반복 할 때 여전히 적용되지 않습니까? 목록을보다 유용하게 만들 수 있다고 말하지만 메모리 문제를 능가하기에 충분한 지 여부는 다른 것입니다.
Rob Grant

181

생성기 표현식 또는 목록 이해 를 반복 하면 동일한 작업이 수행됩니다. 그러나 목록 이해 는 메모리에 전체 목록을 먼저 생성 하고 생성기 표현식 은 즉시 항목을 생성하므로 매우 큰 (및 무한대) 시퀀스에 사용할 수 있습니다.


39
무한대 +1 성능에 대한 관심이 적은 것에 관계없이 목록으로 할 수 없습니다.
Paul Draper

이해 방법을 사용하여 무한 생성기를 만들 수 있습니까?
AnnanFay

5
@Annan 이미 다른 무한 생성기에 액세스 할 수있는 경우에만 해당됩니다. 예를 들어, itertools.count(n)n부터 시작하는 무한 정수 시퀀스이므로 at에서 시작 (2 ** item for item in itertools.count(n))하는 제곱의 무한 시퀀스가 2됩니다 2 ** n.
Kevin

2
생성기는 반복 된 후에 메모리에서 항목을 삭제합니다. 예를 들어, 큰 데이터가있는 경우 표시하기 만하면됩니다. 메모리 호그가 아닙니다. 생성기를 사용하면 항목이 '필요에 따라'처리됩니다. 목록에 매달 리거나 다시 반복하려면 (항목을 저장) 목록 이해를 사용하십시오.
j2emanue

102

결과를 여러 번 반복해야하거나 속도가 가장 중요한 경우 목록 이해를 사용하십시오. 범위가 크거나 무한한 생성기 표현식을 사용하십시오.

자세한 내용은 생성기 표현식 및 목록 이해 를 참조하십시오.


2
이것은 아마도 주제를 조금 벗어난 것이지만 불행히도 "잡을 수없는"것이 될 것입니다. 나는 영어 원어민 아니에요 ... :)
기예르모 아레스

6
@GuillermoAres 이것은 무엇 보다 중요한
Sнаđошƒаӽ

1
그래서 표현 lists보다 더 빠릅 generator니까? dF의 답변을 읽음으로써, 그것은 다른 방향으로 왔다는 것을 알게되었습니다.
Hassan Baig

1
범위가 작을 때 목록 이해력이 더 빠르다고 말하는 것이 더 낫지 만, 스케일이 증가함에 따라 사용 시간에 맞춰 값을 즉시 계산하는 것이 더 가치가 있습니다. 그것이 제너레이터 표현식이하는 일입니다.
Kyle

59

중요한 점은 목록 이해가 새로운 목록을 생성한다는 것입니다. 생성기는 비트를 소비 할 때 소스 자료를 "필터링"하는 반복 가능한 객체를 생성합니다.

"hugefile.txt"라는 2TB 로그 파일이 있고 "ENTRY"라는 단어로 시작하는 모든 행의 내용과 길이를 원한다고 가정하십시오.

따라서 목록 이해력을 작성하여 시작해보십시오.

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

이렇게하면 전체 파일을 정리하고 각 줄을 처리하며 일치하는 줄을 배열에 저장합니다. 따라서이 어레이에는 최대 2TB의 컨텐츠가 포함될 수 있습니다. 그것은 많은 RAM이며 아마도 귀하의 목적에 실용적이지 않을 것입니다.

대신 생성기를 사용하여 콘텐츠에 "필터"를 적용 할 수 있습니다. 결과를 반복하기 시작할 때까지 실제로 데이터를 읽지 않습니다.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

아직 파일에서 한 줄도 읽지 않았습니다. 실제로 결과를 더 필터링하고 싶다고 가정 해보십시오.

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

아직 읽은 것은 없지만 지금 우리가 원하는대로 데이터에 작용할 두 개의 생성기를 지정했습니다.

필터링 된 줄을 다른 파일에 쓸 수 있습니다.

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

이제 입력 파일을 읽습니다. for루프가 계속해서 추가 라인을 요청 함에 따라 long_entries생성기 는 생성기에서 라인을 요구 entry_lines하며 길이가 80자를 초과하는 라인 만 반환합니다. 그리고 entry_lines생성기는 logfile반복자에게 행을 요청하고 (표시된대로 필터링 됨) 파일을 읽습니다.

따라서 완전히 채워진 목록의 형태로 출력 함수에 데이터를 "푸시"하는 대신 출력 함수에 필요할 때만 데이터를 "풀"하는 방법을 제공합니다. 이것은 우리의 경우 훨씬 더 효율적이지만 융통성이 없습니다. 제너레이터는 일방 통행입니다. 읽은 로그 파일의 데이터는 즉시 버려 지므로 이전 줄로 돌아갈 수 없습니다. 반면에 데이터가 완료된 후에는 데이터를 보관하는 것에 대해 걱정할 필요가 없습니다.


46

생성기 표현식의 이점은 전체 목록을 한 번에 작성하지 않기 때문에 메모리를 덜 사용한다는 것입니다. 생성기 표현식은 결과를 합산하거나 결과에서 dict를 작성하는 등 목록이 중간 인 경우에 가장 적합합니다.

예를 들면 다음과 같습니다.

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

이점은 목록이 완전히 생성되지 않아서 메모리가 거의 사용되지 않고 더 빠를 수 있다는 것입니다

그러나 원하는 최종 제품이 목록 인 경우 목록 이해를 사용해야합니다. 생성 된 목록을 원하므로 생성기 표현식을 사용하여 기억을 저장하지 않습니다. 정렬 또는 역순과 같은 목록 기능을 사용할 수 있다는 이점도 있습니다.

예를 들면 다음과 같습니다.

reversed( [x*2 for x in xrange(256)] )

9
생성기 표현식이 그런 식으로 사용되도록 언어로 힌트를 제공합니다. 괄호를 잃어 버리십시오! sum(x*2 for x in xrange(256))
u0b34a0f6ae

8
sorted그리고 reversed어떤 반복 가능한, 발전기 표현에 잘 작동이 포함되어 있습니다.
marr75

1
2.7 이상을 사용할 수있는 경우, dict () 예제는 dict 이해력으로 보입니다 (PEP는 생성기 PEP보다 오래되었지만 착륙하는 데 시간이 오래 걸렸습니다)
Jürgen A. Erhard

14

목록과 같은 가변 객체에서 생성기를 생성 할 때 생성기를 생성 할 때가 아니라 생성기를 사용할 때 목록의 상태에서 생성기가 평가된다는 점에 유의하십시오.

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

목록이 수정 될 가능성이 있거나 생성기 생성시 상태가 필요한 경우 대신 목록 이해를 사용해야합니다.


1
그리고 이것은 받아 들일만한 대답이어야합니다. 데이터가 사용 가능한 메모리보다 큰 경우 메모리의 목록을 반복하는 것이 더 빠를 수 있지만 항상 생성기를 사용해야합니다 (하지만 메모리가 충분하지 않습니다).
Marek Marczak

4

때로는 itertools 에서 tee 함수를 사용하여 벗어날 수 있으며 독립적으로 사용할 수있는 동일한 생성기에 대해 여러 개의 반복자를 반환합니다.


4

Hadoop Mincemeat 모듈을 사용하고 있습니다. 나는 이것이 주목할 좋은 예라고 생각합니다.

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

여기서 생성기는 텍스트 파일에서 숫자 (15GB)를 가져오고 Hadoop의 맵 축소를 사용하여 해당 숫자에 간단한 수학을 적용합니다. yield 함수를 사용하지 않고 목록 이해를 사용했다면 합계와 평균을 계산하는 데 시간이 오래 걸렸을 것입니다 (공간 복잡도는 말할 것도 없습니다).

Hadoop은 Generators의 모든 장점을 사용하는 훌륭한 예입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.