programing tip

Python : 생성기 표현식 대 수율

itbloger 2020. 9. 25. 07:38
반응형

Python : 생성기 표현식 대 수율


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) 등과 같은 튜플을 생성하는 생성기 객체를 반환합니다.

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


모두 고마워요! 이 답변에는 많은 훌륭한 정보와 추가 참조가 있습니다!


둘 사이에는 약간의 차이가 있습니다. 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

이 예에서는 그렇지 않습니다. 그러나 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!")

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

>>> def fibgen():
...    a = b = 1
...    while 1:
...        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]

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

A generator object is use-once-only, in contrast to a generator function, which can be reused each time you call it again, because it returns a fresh generator object.

Generator expressions are in practice usually used "raw", without wrapping them in a function, and they return a generator object.

E.g.:

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()))

which outputs:

[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]

Compare with a slightly different usage:

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

which outputs:

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

And compare with a generator expression:

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))

which also outputs:

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

Using yield is nice if the expression is more complicated than just nested loops. Among other things you can return a special first or special last value. Consider:

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

When thinking about iterators, the itertools module:

... standardizes a core set of fast, memory efficient tools that are useful by themselves or in combination. Together, they form an “iterator algebra” making it possible to construct specialized tools succinctly and efficiently in pure Python.

For performance, consider itertools.product(*iterables[, repeat])

Cartesian product of input iterables.

Equivalent to nested for-loops in a generator expression. For example, product(A, B) returns the same as ((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)]
>>> 

Yes there is a difference.

For the generator expression (x for var in expr), iter(expr) is called when the expression is created.

When using def and yield to create a generator, as in:

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

g = my_generator()

iter(expr) is not yet called. It will be called only when iterating on g (and might not be called at all).

Taking this iterator as an example:

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

This code:

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

while:

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)

Since most iterators do not do a lot of stuff in __iter__, it is easy to miss this behavior. A real world example would be Django's QuerySet, which fetch data in __iter__ and data = (f(x) for x in qs) might take a lot of time, while def g(): for x in qs: yield f(x) followed by data=g() would return immediately.

For more info and the formal definition refer to PEP 289 -- Generator Expressions.


There is a difference that could be important in some contexts that hasn't been pointed out yet. Using yield prevents you from using return for something else than implicitly raising StopIteration (and coroutines related stuff).

This means this code is ill-formed (and feeding it to an interpreter will give you an 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)

On the other hand, this code works like a charm:

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)

참고URL : https://stackoverflow.com/questions/1995418/python-generator-expression-vs-yield

반응형