Python : 생성기 표현 대 수율


90

Python에서 생성기 표현식을 통해 생성기 객체를 만드는 것과 yield 문을 사용하는 것 사이에 차이가 있습니까?

수율 사용 :

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

생성기 표현식 사용 :

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

두 함수 모두 (0,0), (0,1) 등과 같은 튜플을 생성하는 생성기 객체를 반환합니다.

둘 중 하나의 장점이 있습니까? 생각?


감사합니다 여러분! 이 답변에는 많은 훌륭한 정보와 추가 참조가 있습니다!


2
가장 읽기 쉬운 것을 선택하십시오.
user238424 2010 년

답변:


74

둘 사이에는 약간의 차이가 있습니다. dis모듈을 사용하여 이러한 종류를 직접 검사 할 수 있습니다 .

편집 : 내 첫 번째 버전은 대화 형 프롬프트의 모듈 범위에서 생성 된 생성기 표현식을 디 컴파일했습니다. 그것은 함수 내부에서 사용되는 OP 버전과 약간 다릅니다. 질문의 실제 사례와 일치하도록 수정했습니다.

아래에서 볼 수 있듯이 "수율"생성기 (첫 번째 경우)에는 설정에 세 가지 추가 지침이 있지만 첫 번째 FOR_ITER와는 한 가지 측면에서만 다릅니다. "수율"접근 방식은 루프 내부 LOAD_FAST대신 a 를 사용합니다 LOAD_DEREF. 가 LOAD_DEREF있다 "오히려 느린" 보다는 LOAD_FAST경미 속도의 충분히 큰 값 발생기 표현보다는 "수율"버전을 만든다도록 x(외부 루프)의 값으로 인해 y약간 빠른 각 패스에 장착된다. 더 작은 값의 x경우 설정 코드의 추가 오버 헤드로 인해 약간 느려집니다.

생성기 표현식은 일반적으로 이와 같은 함수로 래핑하는 대신 코드에서 인라인으로 사용된다는 점을 지적 할 가치가 있습니다. 이는 약간의 설정 오버 헤드를 제거 LOAD_FAST하고 "yield"버전이 다른 이점을 제공 하더라도 더 작은 루프 값에 대해 생성기 표현식을 약간 더 빠르게 유지합니다 .

두 경우 모두 성능 차이는 둘 중 하나를 결정하는 것을 정당화하기에 충분하지 않습니다. 가독성은 훨씬 더 중요하므로 당면한 상황에서 가장 가독성이 높은 것을 사용하십시오.

>>> def Generator(x, y):
...     for i in xrange(x):
...         for j in xrange(y):
...             yield(i, j)
...
>>> dis.dis(Generator)
  2           0 SETUP_LOOP              54 (to 57)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_FAST                0 (x)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                40 (to 56)
             16 STORE_FAST               2 (i)

  3          19 SETUP_LOOP              31 (to 53)
             22 LOAD_GLOBAL              0 (xrange)
             25 LOAD_FAST                1 (y)
             28 CALL_FUNCTION            1
             31 GET_ITER
        >>   32 FOR_ITER                17 (to 52)
             35 STORE_FAST               3 (j)

  4          38 LOAD_FAST                2 (i)
             41 LOAD_FAST                3 (j)
             44 BUILD_TUPLE              2
             47 YIELD_VALUE
             48 POP_TOP
             49 JUMP_ABSOLUTE           32
        >>   52 POP_BLOCK
        >>   53 JUMP_ABSOLUTE           13
        >>   56 POP_BLOCK
        >>   57 LOAD_CONST               0 (None)
             60 RETURN_VALUE
>>> def Generator_expr(x, y):
...    return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
  2           0 SETUP_LOOP              47 (to 50)
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                40 (to 49)
              9 STORE_FAST               1 (i)
             12 SETUP_LOOP              31 (to 46)
             15 LOAD_GLOBAL              0 (xrange)
             18 LOAD_DEREF               0 (y)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                17 (to 45)
             28 STORE_FAST               2 (j)
             31 LOAD_FAST                1 (i)
             34 LOAD_FAST                2 (j)
             37 BUILD_TUPLE              2
             40 YIELD_VALUE
             41 POP_TOP
             42 JUMP_ABSOLUTE           25
        >>   45 POP_BLOCK
        >>   46 JUMP_ABSOLUTE            6
        >>   49 POP_BLOCK
        >>   50 LOAD_CONST               0 (None)
             53 RETURN_VALUE

허용됨-dis를 사용한 차이점에 대한 자세한 설명입니다. 감사!
cschol 2010 년

LOAD_DEREF"보다 느리다"고 주장하는 소스에 대한 링크를 포함하도록 업데이트했습니다 . 따라서 성능이 실제로 중요한 경우 실제 타이밍이 timeit좋을 것입니다. 이론적 분석은 지금까지만 진행됩니다.
Peter Hansen

36

이 예에서는 그렇지 않습니다. 그러나 yield더 복잡한 구조에 사용할 수 있습니다. 예를 들어 호출자로부터 값을 수락하고 결과적으로 흐름을 수정할 수 있습니다. 읽기 PEP 342 자세한 내용은 (그것이 알고 흥미로운 기술 가치).

어쨌든, 최선의 조언은 당신의 필요에 더 명확한 것을 사용하는 것 입니다.

추신 다음은 Dave Beazley 의 간단한 코 루틴 예제입니다 .

def grep(pattern):
    print "Looking for %s" % pattern
    while True:
        line = (yield)
        if pattern in line:
            print line,

# Example use
if __name__ == '__main__':
    g = grep("python")
    g.next()
    g.send("Yeah, but no, but yeah, but no")
    g.send("A series of tubes")
    g.send("python generators rock!")

8
+1 David Beazley와 연결합니다. 코 루틴에 대한 그의 프레젠테이션은 제가 오랫동안 읽은 것 중 가장 놀라운 것입니다. 발전기에 대한 그의 프레젠테이션만큼 유용하지는 않지만 그럼에도 불구하고 놀랍습니다.
Robert Rossney 2010 년

18

생성기 표현식에 맞출 수있는 간단한 루프의 종류에는 차이가 없습니다. 그러나 yield는 훨씬 더 복잡한 처리를 수행하는 생성기를 만드는 데 사용할 수 있습니다. 다음은 피보나치 수열을 생성하는 간단한 예입니다.

>>> def fibgen():
...    a = b = 1
...    while True:
...        yield a
...        a, b = b, a+b

>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

5
정말 멋진 +1 ... 재귀없이 짧고 달콤한 fib 구현을 본 적이 없다고 말할 수 없습니다.
JudoWill 2010 년

믿을 수 없을 정도로 간단한 코드 스 니펫-피보나치가 그것을보고 기뻐할 것이라고 생각합니다 !!
user-asterix

10

사용법에서 생성기 객체와 생성기 함수의 차이점에 유의하십시오.

생성기 객체는 새로운 생성기 객체를 반환하기 때문에 다시 호출 할 때마다 재사용 할 수있는 생성기 함수와 달리 한 번만 사용됩니다.

생성기 표현식은 실제로 일반적으로 함수로 래핑하지 않고 "원시"로 사용되며 생성기 객체를 반환합니다.

예 :

def range_10_gen_func():
    x = 0
    while x < 10:
        yield x
        x = x + 1

print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))

다음을 출력합니다.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

약간 다른 사용법과 비교하십시오.

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))

다음을 출력합니다.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

생성기 표현식과 비교하십시오.

range_10_gen_expr = (x for x in range(10))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))

또한 다음을 출력합니다.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

8

yield표현식이 중첩 된 루프보다 더 복잡한 경우 사용하는 것이 좋습니다. 무엇보다도 특별한 첫 번째 또는 특별한 마지막 값을 반환 할 수 있습니다. 중히 여기다:

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)

5

반복자에 대해 생각할 때 itertools모듈은 :

... 자체적으로 또는 조합하여 유용한 빠르고 메모리 효율적인 도구의 핵심 세트를 표준화합니다. 이들은 함께 "반복자 대수"를 형성하여 순수 Python에서 전문화 된 도구를 간결하고 효율적으로 구성 할 수 있습니다.

성능을 위해 다음을 고려하십시오. itertools.product(*iterables[, repeat])

입력 이터 러블의 데카르트 곱입니다.

생성기 표현식의 중첩 된 for 루프와 동일합니다. 예를 들어 product(A, B)((x,y) for x in A for y in B).

>>> import itertools
>>> def gen(x,y):
...     return itertools.product(xrange(x),xrange(y))
... 
>>> [t for t in gen(3,2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
>>> 

4

네, 차이가 있습니다.

발전기 표현의 경우 (x for var in expr), iter(expr)표현식이 때 호출되는 생성 .

다음 def과 같이 및 yield생성기를 사용하여 생성기를 만들 때 :

def my_generator():
    for var in expr:
        yield x

g = my_generator()

iter(expr)아직 호출되지 않았습니다. 반복 할 때만 호출됩니다 g(전혀 호출되지 않을 수 있음).

이 반복자를 예로 들면 :

from __future__ import print_function


class CountDown(object):
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        print("ITER")
        return self

    def __next__(self):
        if self.n == 0:
            raise StopIteration()
        self.n -= 1
        return self.n

    next = __next__  # for python2

이 코드 :

g1 = (i ** 2 for i in CountDown(3))  # immediately prints "ITER"
print("Go!")
for x in g1:
    print(x)

동안:

def my_generator():
    for i in CountDown(3):
        yield i ** 2


g2 = my_generator()
print("Go!")
for x in g2:  # "ITER" is only printed here
    print(x)

대부분의 반복자는에서 많은 작업을 수행하지 않기 __iter__때문에이 동작을 놓치기 쉽습니다. 실제 세계의 예는 장고의 것 QuerySet, 어떤 데이터를 가져 오기__iter__data = (f(x) for x in qs)시간이 많이 걸릴 수 있습니다, 동안 def g(): for x in qs: yield f(x)다음 data=g()즉시 반환합니다.

자세한 정보와 공식적인 정의는 PEP 289-Generator Expressions를 참조하십시오 .


0

아직 지적되지 않은 일부 상황에서 중요한 차이가 있습니다. 를 사용 yield하면 암시 적으로 StopIteration (및 코 루틴 관련 항목)을 발생시키는return 것 이외의 다른 용도로 사용할 수 없습니다 .

이것은이 코드의 형식이 잘못되었음을 의미합니다 (통역사에게 제공하면 AttributeError).

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        for item in ['lamp', 'mirror', 'coat rack', 'tape measure', 'ficus']:
            yield item

print(mary_poppins_purse(True).temperature)

반면에이 코드는 매력처럼 작동합니다.

class Tea:

    """With a cloud of milk, please"""

    def __init__(self, temperature):
        self.temperature = temperature

def mary_poppins_purse(tea_time=False):
    """I would like to make one thing clear: I never explain anything."""
    if tea_time:
        return Tea(355)
    else:
        return (item for item in ['lamp', 'mirror', 'coat rack',
                                  'tape measure', 'ficus'])

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