In answering this question, I stumbled across some unexpected behavior:
from typing import List, Iterable
class Name:
def __init__(self, name: str):
self.name = name
def generator(lst: List[Name]) -> Iterable[str]:
lst_copy = lst.copy()
for obj in lst_copy:
yield obj.name
When modifying the list that is passed to the generator, even though a copy is made, changes to the original list are still reflected:
lst = [Name("Tom"), Name("Tommy")]
gen = generator(lst)
lst[0] = Name("Andrea")
for name in gen:
print(name)
Output:
Andrea
Tommy
Simply returning a generator expression works as expected:
def generator(lst: List[Name]) -> Iterable[str]:
return (obj.name for obj in lst.copy())
Output:
Tom
Tommy
Why doesn't the lst.copy()
in the first generator function work as expected?
I think the behavior is best understood with the addition of some extra print statements:
def generator(lst: List[Name]) -> Iterable[str]:
print("Creating list copy...")
lst_copy = lst.copy()
print("Created list copy!")
for obj in lst_copy:
yield obj.name
lst = [Name("Tom"), Name("Tommy")]
print("Starting assignment...")
gen = generator(lst)
print("Assignment complete!")
print("Modifying list...")
lst[0] = Name("Andrea")
print("Modification complete!")
for name in gen:
print(name)
Notice that the copy does not happen at assignment time -- it happens after the list is modified!
Starting assignment...
Assignment complete!
Modifying list...
Modification complete!
Creating list copy...
Created list copy!
Andrea
Tommy
Nothing in the generator's body is executed until the for
loop attempts to extract an element. Since this extraction attempt occurs after the list is mutated, the mutation is reflected in the results from the generator.