파이썬 생성기를 사용하기에 좋은시기가 아닐까요?


83

이것은 Python 생성기 함수를 무엇에 사용할 수 있습니까? 의 역입니다 . : 파이썬 제너레이터, 제너레이터 표현식, itertools모듈은 요즘 제가 가장 좋아하는 파이썬 기능 중 일부입니다. 대용량 데이터에 대해 수행 할 작업 체인을 설정할 때 특히 유용합니다. DSV 파일을 처리 할 때 자주 사용합니다.

그래서 때입니다 하지 발전기 또는 발전기의 표현, 또는 사용할 수있는 좋은 시간 itertools기능은?

  • 언제 선호한다 zip()이상 itertools.izip(), 또는
  • range()이상 xrange()또는
  • [x for x in foo]이상 (x for x in foo)?

분명히, 우리는 결국 생성자를 실제 데이터로 "해결"해야합니다. 일반적으로 목록을 만들거나 생성자가 아닌 루프를 반복하여 생성합니다. 때로는 길이 만 알면됩니다. 이것은 내가 요구하는 것이 아닙니다.

중간 데이터를 위해 새 목록을 메모리에 할당하지 않도록 생성기를 사용합니다. 이것은 특히 대규모 데이터 세트에 적합합니다. 작은 데이터 세트에도 이치에 맞습니까? 눈에 띄는 메모리 / CPU 트레이드 오프가 있습니까?

특히 list comprehension 성능 대 map () 및 filter () 에 대한 눈에 띄는 토론에 비추어 누군가가 이것에 대해 프로파일 링을 해본 적이 있다면 특히 관심이 있습니다. ( 대체 링크 )


2
나는 여기에 비슷한 질문을 제기하고 내 특정 예제 목록에서 길이 반복에 대해 더 빠르다는<5 것을 찾기 위해 몇 가지 분석을 수행했습니다 .
Alexander McFarlane 2016 년

이것이 귀하의 질문에 대답합니까? 생성기 표현 대 목록 이해
ggorlen

답변:


57

다음과 같은 경우 생성기 대신 목록을 사용하십시오.

1) 데이터에 여러 번 액세스해야합니다 (즉, 결과를 다시 계산하는 대신 캐시) :

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) 임의 액세스 (또는 순차 순차 이외의 액세스 )가 필요합니다 .

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) 문자열 을 조인 해야합니다 (데이터를 두 번 통과해야 함).

s = ''.join(data)                # lists are faster than generators in this use case

4) PyPy 를 사용 하고 있습니다. 때때로 일반적인 함수 호출 및 목록 조작으로 생성기 코드를 최적화 할 수 없습니다.


# 3의 ireduce경우 조인을 복제하는 데 사용하여 두 패스를 피할 수 없었 습니까?
Platinum Azure

감사! 나는 문자열 결합 동작을 인식하지 못했습니다. 두 번의 패스가 필요한 이유에 대한 설명을 제공하거나 링크 할 수 있습니까?
David Eyk 2014 년

5
@DavidEyk str.join 은 모든 문자열 조각의 길이를 합산하기 위해 한 번의 패스를 수행하므로 결합 된 최종 결과에 할당 할 메모리를 많이 알 수 있습니다. 두 번째 패스는 문자열 조각을 새 버퍼에 복사하여 단일 새 문자열을 만듭니다. hg.python.org/cpython/file/82fd95c2851b/Objects/stringlib/…
Raymond Hettinger

1
흥미롭게도 저는 srings에 합류하기 위해 발전기를 자주 사용합니다. 그러나 두 번의 패스가 필요한 경우 어떻게 작동합니까? 예를 들어''.join('%s' % i for i in xrange(10))
bgusach

4
@ ikaros45 조인 입력 이 목록이 아닌 경우 두 패스에 대한 임시 목록을 작성하기 위해 추가 작업을 수행해야합니다. 대략이``data = data if isinstance (data, list) else list (data); n = sum (map (len, data)); 버퍼 = 바이트 배열 (n); ... <조각을 버퍼로 복사>```.
Raymond Hettinger 2014

40

일반적으로 len (), reversed () 등과 같은 목록 작업이 필요한 경우 생성기를 사용하지 마십시오.

지연 평가를 원하지 않는 경우도 있습니다 (예 : 리소스를 해제 할 수 있도록 모든 계산을 미리 수행). 이 경우 목록식이 더 좋을 수 있습니다.


25
또한 모든 계산을 미리 수행하면 목록 요소의 계산에서 예외가 발생 하는 경우 이후에 반복되는 루프가 아니라 목록이 생성 된 지점에서 예외가 발생 합니다. 계속하기 전에 전체 목록을 오류없이 처리해야하는 경우 생성기는 좋지 않습니다.
Ryan C. Thompson

4
그건 좋은 지적이야. 발전기를 처리하는 도중에 모든 것이 폭발하는 것은 매우 실망 스럽습니다. 잠재적으로 위험 할 수 있습니다.
David Eyk

26

프로필, 프로필, 프로필.

코드를 프로파일 링하는 것은 현재 수행중인 작업이 효과가 있는지 알 수있는 유일한 방법입니다.

xrange, 생성기 등의 대부분의 사용은 정적 크기, 작은 데이터 세트 이상입니다. 큰 데이터 세트에 도달 할 때만 실제로 차이를 만듭니다. range () 대 xrange ()는 대부분 코드를 조금 더보기 흉하게 만들고 아무것도 잃지 않고 무언가를 얻는 문제 일뿐입니다.

프로필, 프로필, 프로필.


1
실제로 프로필. 언젠가는 실증적 인 비교를 해보겠습니다. 그때까지는 다른 사람이 이미 가지고 있기를 바랐습니다. :)
David Eyk

프로필, 프로필, 프로필. 완전히 동의 해. 프로필, 프로필, 프로필.
Jeppe

17

당신은 선호해서는 안 zip이상 izip, range이상 xrange, 또는 발전기 함축을 통해 지능형리스트. 파이썬 3.0에서는 rangexrange-like 의미를하고 zip있다 izip-like 의미를.

목록 이해력은 list(frob(x) for x in foo)실제 목록이 필요한 때 처럼 실제로 더 명확 합니다.


3
@Steven 나는 동의하지 않지만 귀하의 답변 뒤에있는 이유가 무엇인지 궁금합니다. zip, range 및 list comprehensions가 해당 "lazy"버전보다 선호되지 않는 이유는 무엇입니까?
mhawke

그가 말했듯이 zip 및 range의 이전 동작은 곧 사라질 것이기 때문입니다.

@Steven : 좋은 지적입니다. 나는 3.0의 이러한 변화에 대해 잊었을 것입니다. 이것은 아마도 누군가가 그들의 일반적인 우월성을 확신한다는 것을 의미 할 것입니다. Re : 목록 이해, 그들은 종종 더 명확하고 (확장 for루프 보다 빠릅니다 !), 이해하기 어려운 목록 이해를 쉽게 작성할 수 있습니다.
David Eyk

9
나는 당신이 의미하는 바를 알지만 []형식이 충분히 설명 적이라는 것을 알았 습니다 (일반적으로 더 간결하고 덜 복잡합니다). 그러나 이것은 단지 맛의 문제입니다.
David Eyk

4
목록 작업은 작은 데이터 크기의 경우 더 빠르지 만 데이터 크기가 작은 경우 모든 것이 빠르므로 목록을 사용해야하는 특별한 이유가없는 한 항상 생성기를 선호해야합니다 (이러한 이유로 Ryan Ginstrom의 답변 참조).
Ryan C. Thompson

7

"이것은 특히 대규모 데이터 세트에 적합합니다"라고 언급했듯이 이것이 귀하의 질문에 대한 답이라고 생각합니다.

성능 측면에서 벽에 부딪히지 않는 경우에도 목록과 표준 기능을 고수 할 수 있습니다. 그런 다음 성능 문제가 발생하면 전환하십시오.

그러나 주석에서 @ u0b34a0f6ae가 언급했듯이 처음에 생성기를 사용하면 더 큰 데이터 세트로 쉽게 확장 할 수 있습니다.


5
+1 Generators를 사용하면 예상 할 필요없이 큰 데이터 세트에 대한 코드를 더욱 준비 할 수 있습니다.
u0b34a0f6ae

6

성능과 관련하여 : psyco를 사용하면 목록이 생성기보다 훨씬 빠를 수 있습니다. 아래 예에서 목록은 psyco.full ()을 사용할 때 거의 50 % 더 빠릅니다.

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    elif not num % 3:
        return "%d fizz" % num
    elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

결과 :

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds

1
그것은 psyco가 발전기의 속도를 전혀 높이 지 않기 때문에 발전기보다 psyco의 단점에 더 가깝습니다. 그래도 좋은 대답입니다.
Steven Huwig

4
또한 psyco는 현재 거의 유지되지 않습니다. 모든 개발자는 PyPy의 JIT에 시간을 투자하여 내 지식을 최대한 활용하여 생성기를 최적화합니다.
Noufal Ibrahim

3

성능에 관한 한 생성자보다 목록을 사용하고 싶을 때를 생각할 수 없습니다.


all(True for _ in range(10 ** 8))all([True for _ in range(10 ** 8)])Python 3.8 보다 느립니다 . 여기에 발전기보다 목록을 선호합니다
ggorlen

3

발전기가 당신이하려는 일을 방해하는 상황을 본 적이 없습니다. 그러나 생성기를 사용하지 않는 것보다 더 도움이되지 않는 경우가 많이 있습니다.

예를 들면 :

sorted(xrange(5))

다음에 대해 개선 사항을 제공하지 않습니다.

sorted(range(5))

4
range(5)결과 목록이 이미 정렬 되었기 때문에 둘 다 .
dan04

3

나중에 다른 값을 유지해야하고 세트의 크기가 너무 크지 않은 경우 목록 이해를 선호해야합니다.

예를 들어, 나중에 프로그램에서 여러 번 반복 할 목록을 만들고 있습니다.

어느 정도까지는 제너레이터를 반복 (루프)의 대체물로 생각할 수 있고, 목록 내포물은 데이터 구조 초기화의 한 유형으로 생각할 수 있습니다. 데이터 구조를 유지하려면 목록 이해를 사용하십시오.


스트림에서 제한된 미리보기 / 뒤보기 만 필요한 경우 itertools.tee()도움이 될 수 있습니다. 그러나 일반적으로 두 개 이상의 패스 또는 일부 중간 데이터에 대한 임의 액세스를 원하면 목록 / 세트 / 사전을 만드십시오.
Beni Cherniavsky-Paskin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.