2014-10-01 3 views
2

Я пытаюсь понять разницу между функциями и генераторами и использовал для этого код ниже. Однако я не понимаю результаты.Разница между генераторами и функциями в Python

class GeneratorsSample(object): 

    def DoReturn(self): 
     counter, maxCounter = 0, 5 
     listResults = [] 
     while counter < maxCounter: 
      print "tic" 
      listResults.append(counter*2) 
      counter += 1 
     return listResults 

    def DoYield(self): 
     counter, maxCounter = 0, 5 
     listResults = [] 
     while counter < maxCounter: 
      print "tic" 
      listResults.append(counter*2) 
      yield listResults #to return the entire list 
      #yield listResults[counter] #you can only return one item at a time 
      counter += 1 
     return 


generatorSample = GeneratorsSample() 

Я не понимаю, почему, то выход для DoReturn() отличается от такового DoYield(). Например,

returnResults = generatorSample.DoReturn() 
for r in returnResults: 
    print "toc", r 

Выходы:

tic 
tic 
tic 
tic 
tic 
toc 0 
toc 2 
toc 4 
toc 6 
toc 8 

И

yieldResults = generatorSample.DoYield() 
for r in yieldResults:  
    print "toc", r 

Выходы:

tic 
toc [0] 
tic 
toc [0, 2] 
tic 
toc [0, 2, 4] 
tic 
toc [0, 2, 4, 6] 
tic 
toc [0, 2, 4, 6, 8] 
+3

Вы строите список внутри генератора, но вы (вероятно) * просто * хочу уступить значения. Вместо этого вы уступаете списку по мере его создания, что приводит к различию. Например, в 'DoYield()' вам вообще не нужны 'listResults', вы должны просто 'yield counter * 2' напрямую. –

+0

@TomDalton Спасибо. Я думаю, что я понимаю и что вы имеете в виду. Тем не менее, я ожидал, что запуск DoYield() 'приведет к' tic [0] '' tic [0,2] '' tic [0,2,4] 'и т. Д., А затем' toc [0] 'toc [0,2]' и т. д. Я не понимаю, почему 'toc' печатается между операторами' tic'. – user131983

+0

@ user131983 рассмотрите возможность запуска кода в нижней части моего ответа, посмотрите, помогает ли это объяснять, почему 'toc' печатается между операторами' tic' – jedwards

ответ

2

Это, вероятно, лучший пример:

class GeneratorsSample(object): 
    def DoReturn(self): 
     counter, maxCounter = 0, 5 
     listResults = [] 
     while counter < maxCounter: 
      print "tic" 
      listResults.append(counter*2) 
      counter += 1 
     return listResults 

    def DoYield(self): 
     counter, maxCounter = 0, 5 
     while counter < maxCounter: 
      print "tic" 
      yield counter*2 
      counter += 1 
     return 


generatorSample = GeneratorsSample() 

ret = generatorSample.DoReturn() 
yld = generatorSample.DoYield() 

for r in ret: print "toc", r 
for r in yld: print "toc", r 

print ret 
print yld 

Первый взгляд на эти строки:

for r in ret: print "toc", r 
for r in yld: print "toc", r 

Те же значения генерируются, но в «обратном» версии, тики все приходят, а затем все КТВ. В версии «выход» тики и токи перемежаются.

Но главное различие между этими двумя способами проиллюстрированы следующими строками:

print ret # prints: [0, 2, 4, 6, 8] 
print yld # prints: <generator object DoYield at 0x0000000002202630> 

Здесь ret список всех значений сгенерированных. То есть, когда это назначение сделано:

ret = generatorSample.DoReturn() 

Весь список генерируется затем и вернулся в полном списке.

С генераторным подходом весь список не сгенерированный, по сути, ни один элемент не вычисляется. Просто ссылка на генератор, который будет производить элементы «только муха», по мере необходимости.

Другими словами, «возврат» подход:

 
generates a number 
generates a number 
generates a number 
... 
uses that number 
uses that number 
uses that number 
... 

в то время как подход генератора:

 
generates a number 
uses that number 
generates a number 
uses that number 
... 

Эффективность генераторов в том, что они только принимают время генерации одного элемент, поскольку они необходимы (, если они когда-либо необходимы). Если maxCounter был, скажем, 1 миллионом, а вычисление было более сложным, чем counter * 2, то было бы заметно увеличить время, затрачиваемое на получение первого результата.

В самом деле, вы можете видеть, что при добавлении искусственных задержек (здесь, с time.sleep(1):

import time 

class GeneratorsSample(object): 
    def DoReturn(self): 
     counter, maxCounter = 0, 5 
     listResults = [] 
     while counter < maxCounter: 
      v = counter * 2 
      time.sleep(1) 
      print "[DoReturn] computed %d" % v 
      listResults.append(v) 
      counter += 1 
     return listResults 

    def DoYield(self): 
     counter, maxCounter = 0, 5 
     while counter < maxCounter: 
      v = counter * 2 
      time.sleep(1) 
      print "[DoYield] computed %d" % v 
      yield counter*2 
      counter += 1 

     return 


generatorSample = GeneratorsSample() 

ret = generatorSample.DoReturn() 
yld = generatorSample.DoYield() 

for r in ret: print "[return loop] using", r 
print("") 
for r in yld: print "[yield loop] using", r 

Выход существо:

 
[DoReturn] computed 0 
[DoReturn] computed 2 
[DoReturn] computed 4 
[DoReturn] computed 6 
[DoReturn] computed 8 
[return loop] using 0 
[return loop] using 2 
[return loop] using 4 
[return loop] using 6 
[return loop] using 8 

[DoYield] computed 0 
[yield loop] using 0 
[DoYield] computed 2 
[yield loop] using 2 
[DoYield] computed 4 
[yield loop] using 4 
[DoYield] computed 6 
[yield loop] using 6 
[DoYield] computed 8 
[yield loop] using 8 
+0

Большое спасибо. Отличное объяснение. – user131983

Смежные вопросы