В Python есть ли разница между созданием объекта- генератора с помощью выражения генератора и использованием оператора yield ?
Использование 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) и т. Д.
Какие преимущества того или другого? Мысли?
Спасибо всем! В этих ответах много полезной информации и дополнительных ссылок!
python
python-3.x
generator
yield
cschol
источник
источник
Ответы:
Между ними есть лишь небольшие различия. Вы можете использовать этот
dis
модуль, чтобы самостоятельно изучить подобные вещи.Изменить: Моя первая версия декомпилировала выражение генератора, созданное в области модуля в интерактивной подсказке. Это немного отличается от версии OP, поскольку она используется внутри функции. Я изменил это, чтобы соответствовать фактическому случаю в вопросе.
Как вы можете видеть ниже, генератор «yield» (первый случай) имеет три дополнительных инструкции в настройке, но с первого
FOR_ITER
раза они отличаются только в одном отношении: подход «yield» использует внутри циклаLOAD_FAST
вместо aLOAD_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
источник
LOAD_DEREF
он «довольно медленнее», поэтому, если производительность действительно имеет значение,timeit
было бы неплохо использовать реальное время . Теоретический анализ идет только до этого момента.В этом примере нет. Но
yield
может использоваться для более сложных конструкций - например, он также может принимать значения от вызывающего объекта и в результате изменять поток. Прочтите PEP 342 для получения более подробной информации (это интересный метод, который стоит знать).В любом случае, лучший совет - используйте то, что более четко соответствует вашим потребностям .
PS Вот простой пример сопрограммы от Дэйва Бизли :
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 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]
источник
Обратите внимание на различие между объектом-генератором и функцией-генератором.
Объект-генератор используется только один раз, в отличие от функции-генератора, которую можно повторно использовать каждый раз, когда вы вызываете ее снова, поскольку она возвращает новый объект-генератор.
Выражения генератора на практике обычно используются "в сыром виде", без включения их в функцию, и они возвращают объект генератора.
Например:
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] [] []
источник
Использование
yield
удобно, если выражение сложнее, чем просто вложенные циклы. Помимо прочего, вы можете вернуть особое первое или особое последнее значение. Рассматривать:def Generator(x): for i in xrange(x): yield(i) yield(None)
источник
Размышляя об итераторах,
itertools
модуль:Для производительности рассмотрите
itertools.product(*iterables[, repeat])
>>> 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__
, это поведение легко пропустить. В качестве реального примера можно привести DjangoQuerySet
, который извлекает данные__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)
источник