Hãy đơn giản hóa câu hỏi. Định nghĩa:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Sau đó, giống như trong câu hỏi, chúng tôi nhận được:
>>> for name, f in list(get_petters()):
... print(name + ":", f())
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Nhưng nếu chúng ta tránh tạo ra một list()
đầu tiên:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
Chuyện gì vậy? Tại sao sự khác biệt tinh tế này lại thay đổi hoàn toàn kết quả của chúng ta?
Nếu chúng ta nhìn vào list(get_petters())
, rõ ràng là từ các địa chỉ bộ nhớ thay đổi, chúng ta thực sự mang lại ba chức năng khác nhau:
>>> list(get_petters())
[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]
Tuy nhiên, hãy xem cell
các hàm này được liên kết với:
>>> for _, f in list(get_petters()):
... print(f(), f.__closure__)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
>>> for _, f in get_petters():
... print(f(), f.__closure__)
Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)
Đối với cả hai vòng lặp, cell
đối tượng được giữ nguyên trong suốt các lần lặp. Tuy nhiên, như mong đợi, str
nó tham chiếu cụ thể khác nhau trong vòng lặp thứ hai. Đối cell
tượng tham chiếu đến animal
, được tạo ra khi get_petters()
được gọi. Tuy nhiên, animal
thay đổi str
đối tượng mà nó đề cập đến khi chạy hàm máy phát điện .
Trong vòng lặp đầu tiên, trong mỗi lần lặp, chúng tôi tạo tất cả các f
s, nhưng chúng tôi chỉ gọi chúng sau khi trình tạo get_petters()
hoàn toàn hết và một list
trong các hàm đã được tạo.
Trong vòng lặp thứ hai, trong mỗi lần lặp, chúng tôi tạm dừng trình get_petters()
tạo và gọi f
sau mỗi lần tạm dừng. Do đó, chúng ta kết thúc việc truy xuất giá trị của animal
tại thời điểm mà hàm trình tạo bị tạm dừng.
Như @Claudiu đưa ra câu trả lời cho một câu hỏi tương tự :
Ba hàm riêng biệt được tạo ra, nhưng mỗi hàm đều có sự đóng lại của môi trường mà chúng được định nghĩa - trong trường hợp này là môi trường toàn cục (hoặc môi trường của hàm bên ngoài nếu vòng lặp được đặt bên trong một hàm khác). Tuy nhiên, đây chính xác là vấn đề - trong môi trường này, đã animal
bị đột biến, và các lần đóng đều đề cập đến như nhau animal
.
[Ghi chú của người biên tập: i
đã được đổi thành animal
.]
for animal in ['cat', 'dog', 'cow']
... tôi chắc chắn rằng ai đó sẽ đi cùng và giải thích điều này - đó là một trong những gotcha của Python :)