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) и т. д.
какие-либо преимущества одного или другого? Мысли?
спасибо всем! В этих ответах есть много отличной информации и дальнейших ссылок!
8 ответов:
есть только небольшие различия в двух. Вы можете использовать
disмодуль для изучения такого рода вещи для себя.Edit: моя первая версия декомпиляция выражение генератор создан на модуль-сферу в интерактивную командную строку. Это немного отличается от версии OP, когда она используется внутри функции. Я изменил это, чтобы соответствовать фактическому случаю в вопросе.
как вы можете видеть ниже, генератор "выход" (первый case) имеет три дополнительные инструкции в настройке, но с первого
FOR_ITERони отличаются только в одном: "выход" подход используетLOAD_FASTвместо aLOAD_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'sQuerySet, который выборка данных в__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