MaxLunar
MaxLunar

Reputation: 663

Strange behavior of generator from function

While playing with generators, I found interesting thing. When I defined a function with yield keyword, received a generator from it, I also deleted the variable with sequence, which was fed to function. And *POOF!* - generator become empty. Here's my steps:

>>> def foo(list_):
...     for i in list_:
...             yield i*2
... 
>>> val = [1, 2, 3, 4]
>>> gen = foo(val)
>>> print(tuple(gen))
(2, 4, 6, 8)
>>> del val
>>> print(tuple(gen))
()
>>> 

Shouldn't it be immutable? Or, if it actually works as object which feeds all values of it's variable to it's function, giving the output, why it didn't throws an exception due to absence of linked sequence? Actually, that example can be explained as if I iterate over a empty sequence, which results that block for _ in []: wouldn't ever start. But, I cant explain why this does not throw an exception:

>>> def foo(list_):
...     for i in list_:
...             yield i*2
... 
>>> val = [1, 2, 3, 4]
>>> gen = foo(val)
>>> print(tuple(gen))
(2, 4, 6, 8)
>>> del foo
>>> print(tuple(gen))
()
>>> 

Are generators act here similar to dict.get() function? I don't understand this.

Upvotes: 0

Views: 75

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121644

This has nothing to do with objects being deleted. Instead, you have exhausted the generator; generators can only be iterated over once. Create a new generator from the generator function if you need to iterate a second time. You see the same behaviour without deleting references:

>>> def foo(list_):
...     for i in list_:
...         yield i*2
...
>>> val = [1, 2, 3, 4]
>>> gen = foo(val)
>>> list(gen)
[2, 4, 6, 8]
>>> list(gen)  # gen is now exhausted, so no more elements are produced
[]
>>> gen = foo(val)  # new generator
>>> list(gen)
[2, 4, 6, 8]

Note that del foo or del val only deletes the reference to an object. It wouldn't delete the function or list object altogether if there are other references to it, like from an existing generator created from the function. So gen = foo(val); del foo, val won't break the gen object, it can still produce values without either the foo or val references existing, because gen itself still references what it needs to complete:

>>> gen = foo(val)
>>> del foo, val
>>> list(gen)  # gen still references the list and code objects
[2, 4, 6, 8]

Upvotes: 4

Related Questions