Python: выражение генератора и выход



в Python, есть ли разница между созданием объекта генератор через выражение генератор и с помощью доходность заявление?



используя доходность:



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) и т. д.



какие-либо преимущества одного или другого? Мысли?





спасибо всем! В этих ответах есть много отличной информации и дальнейших ссылок!

442   8  

8 ответов:

есть только небольшие различия в двух. Вы можете использовать dis модуль для изучения такого рода вещи для себя.

Edit: моя первая версия декомпиляция выражение генератор создан на модуль-сферу в интерактивную командную строку. Это немного отличается от версии OP, когда она используется внутри функции. Я изменил это, чтобы соответствовать фактическому случаю в вопросе.

как вы можете видеть ниже, генератор "выход" (первый case) имеет три дополнительные инструкции в настройке, но с первого FOR_ITER они отличаются только в одном: "выход" подход использует LOAD_FAST вместо a LOAD_DEREF внутри цикла. Элемент LOAD_DEREF и ", а медленные" чем LOAD_FAST, поэтому он делает версию "yield" немного быстрее, чем выражение генератора для достаточно больших значений x (внешний цикл) потому что значение y загружается немного быстрее на каждом проходе. Для меньших значений x будет немного медленнее из-за дополнительных накладных расходов кода установки.

возможно, также стоит отметить, что выражение генератора обычно будет использоваться в коде, а не обертывать его с помощью такой функции. Это удалило бы немного накладных расходов на настройку и сохранило бы выражение генератора немного быстрее для меньших значений цикла, даже если LOAD_FAST дал" выход " версии преимущество в противном случае.

в обоих случаях будет производительность разница будет достаточно, чтобы оправдать выбор между одним или другим. Читаемость имеет гораздо большее значение, поэтому используйте то, что кажется наиболее читаемым для данной ситуации.

>>> 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 для более подробной информации (это интересный метод стоит знать).

в любом случае, лучший совет-это используйте все, что яснее для ваших нужд.

P. S. Вот простой пример сопрограммы от Дейв Бизли:

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

нет никакой разницы для вида простых циклов, которые вы можете вписать в выражение генератора. Однако выход может быть использован для создания генераторов, которые делают гораздо более сложную обработку. Вот простой пример для генерации последовательности Фибоначчи:

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

используя yield приятно, если выражение является более сложным, чем просто вложенные циклы. Помимо прочего, вы можете вернуть специальное первое или специальное последнее значение. Рассмотрим:

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

при использовании обратите внимание на различие между объектом генератора и функцией генератора.

объект генератора используется только один раз, в отличие от функции генератора, которая может быть повторно использована каждый раз, когда вы вызываете ее снова, потому что она возвращает новый объект генератора.

выражения генератора на практике обычно используются "raw", без обертывания их в функцию, и они возвращают объект генератора.

например:

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

когда думаешь об итераторах, то 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)]
>>> 

Да есть разница.

для выражения генератор (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__, это легко пропустить такое поведение. Примером реального мира был бы Django's QuerySet, который выборка данных в __iter__ и data = (f(x) for x in qs) может занять много времени, в то время как def g(): for x in qs: yield f(x) следовал по data=g() немедленно вернуться.

для получения дополнительной информации и формального определения см. PEP 289 -- генератор выражений.

есть разница, которая может быть важна в некоторых контекстах, которые еще не были указаны. Используя yield предотвращает использование return на что-то другое, чем неявно поднимая StopIteration (и сопрограммы связанные вещи).

это означает, что этот код плохо сформирован (и подача его интерпретатору даст вам 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)

Comments

    Ничего не найдено.