Giải thích
Vấn đề ở đây là giá trị của i
không được lưu khi hàm f
được tạo. Thay vào đó, f
tìm kiếm giá trị của i
thời điểm nó được gọi .
Nếu bạn nghĩ về nó, hành vi này có ý nghĩa hoàn hảo. Trên thực tế, đó là cách hợp lý duy nhất mà các chức năng có thể hoạt động. Hãy tưởng tượng bạn có một hàm truy cập một biến toàn cục, như sau:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
Khi bạn đọc mã này, tất nhiên - bạn sẽ mong đợi nó in "bar", không phải "foo", vì giá trị của global_var
đã thay đổi sau khi hàm được khai báo. Điều tương tự cũng đang xảy ra trong mã của riêng bạn: Vào thời điểm bạn gọi f
, giá trị của i
đã thay đổi và được đặt thành2
.
Giải pháp
Thực tế có nhiều cách để giải quyết vấn đề này. Dưới đây là một số tùy chọn:
Buộc ràng buộc sớm của i
bằng cách sử dụng nó làm đối số mặc định
Không giống như các biến đóng (như i
), các đối số mặc định được đánh giá ngay lập tức khi hàm được định nghĩa:
for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)
Để cung cấp một chút thông tin chi tiết về cách / tại sao điều này hoạt động: Các đối số mặc định của một hàm được lưu trữ dưới dạng một thuộc tính của hàm; do đó giá trị hiện tại của i
được chụp nhanh và lưu lại.
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__ # this is where the current value of i is stored
(0,)
>>> # assigning a new value to i has no effect on the function's default arguments
>>> i = 5
>>> f.__defaults__
(0,)
Sử dụng một nhà máy chức năng để nắm bắt giá trị hiện tại của i
một lần đóng
Gốc của vấn đề của bạn i
là một biến số có thể thay đổi. Chúng ta có thể giải quyết vấn đề này bằng cách tạo một biến khác được đảm bảo sẽ không bao giờ thay đổi - và cách dễ nhất để làm điều này là đóng :
def f_factory(i):
def f():
return i # i is now a *local* variable of f_factory and can't ever change
return f
for i in range(3):
f = f_factory(i)
functions.append(f)
Sử dụng functools.partial
để ràng buộc giá trị hiện tại của i
đểf
functools.partial
cho phép bạn đính kèm các đối số vào một hàm hiện có. Theo một cách nào đó, nó cũng là một loại nhà máy chức năng.
import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i) # important: use a different variable than "f"
functions.append(f_with_i)
Lưu ý: Các giải pháp này chỉ hoạt động nếu bạn gán giá trị mới cho biến. Nếu bạn sửa đổi đối tượng được lưu trữ trong biến, bạn sẽ gặp lại vấn đề tương tự:
>>> i = [] # instead of an int, i is now a *mutable* object
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5) # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]
Lưu ý rằng cách i
vẫn thay đổi mặc dù chúng tôi đã biến nó thành một đối số mặc định! Nếu mã của bạn thay đổi i
, thì bạn phải liên kết một bản sao của i
hàm của mình, như sau:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())