คำอธิบาย
ปัญหาที่นี่เป็นที่ค่าของi
ไม่ได้ถูกบันทึกเมื่อฟังก์ชั่นf
จะถูกสร้างขึ้น แต่f
เงยหน้าขึ้นค่าของi
เมื่อมันถูกเรียกว่า
หากคุณลองคิดดูพฤติกรรมนี้ก็สมเหตุสมผลดี ในความเป็นจริงมันเป็นวิธีเดียวที่ฟังก์ชั่นการทำงานที่สมเหตุสมผล สมมติว่าคุณมีฟังก์ชันที่เข้าถึงตัวแปรส่วนกลางเช่นนี้
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
เมื่อคุณอ่านโค้ดนี้คุณต้องคาดหวังว่ามันจะพิมพ์ "bar" ไม่ใช่ "foo" เนื่องจากค่าของglobal_var
มีการเปลี่ยนแปลงหลังจากที่มีการประกาศฟังก์ชัน สิ่งเดียวกันที่เกิดขึ้นในรหัสของคุณเอง: เมื่อถึงเวลาที่คุณโทรหาf
ค่าของการมีการเปลี่ยนแปลงและได้รับการตั้งค่าi
2
การแก้ไขปัญหา
มีหลายวิธีในการแก้ปัญหานี้ นี่คือตัวเลือกบางส่วน:
บังคับใช้การเชื่อมโยงก่อนกำหนดi
โดยใช้เป็นอาร์กิวเมนต์เริ่มต้น
ซึ่งแตกต่างจากตัวแปรปิด (เช่นi
) อาร์กิวเมนต์เริ่มต้นจะได้รับการประเมินทันทีเมื่อกำหนดฟังก์ชัน:
for i in range(3):
def f(i=i):
return i
functions.append(f)
เพื่อให้ข้อมูลเชิงลึกเล็กน้อยเกี่ยวกับวิธีการทำงาน: อาร์กิวเมนต์เริ่มต้นของฟังก์ชันจะถูกเก็บไว้เป็นแอตทริบิวต์ของฟังก์ชัน ดังนั้นค่าปัจจุบันของi
จึงถูก snapshotted และบันทึกไว้
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__
(0,)
>>>
>>> i = 5
>>> f.__defaults__
(0,)
ใช้โรงงานฟังก์ชันเพื่อจับค่าปัจจุบันของi
การปิด
ต้นตอของปัญหาของคุณi
คือตัวแปรที่เปลี่ยนแปลงได้ เราสามารถแก้ไขปัญหานี้ได้โดยการสร้างตัวแปรอื่นที่รับประกันว่าจะไม่มีวันเปลี่ยนแปลงและวิธีที่ง่ายที่สุดในการดำเนินการนี้คือการปิด :
def f_factory(i):
def f():
return i
return f
for i in range(3):
f = f_factory(i)
functions.append(f)
ใช้functools.partial
เพื่อผูกค่าปัจจุบันของi
ถึงf
functools.partial
ช่วยให้คุณสามารถแนบอาร์กิวเมนต์กับฟังก์ชันที่มีอยู่ ในทางหนึ่งมันก็เป็นโรงงานฟังก์ชันเช่นกัน
import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i)
functions.append(f_with_i)
คำเตือน:โซลูชันเหล่านี้ใช้ได้เฉพาะเมื่อคุณกำหนดค่าใหม่ให้กับตัวแปร หากคุณแก้ไขวัตถุที่เก็บไว้ในตัวแปรคุณจะพบปัญหาเดิมอีกครั้ง:
>>> i = []
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5)
>>> f()
i = [5]
สังเกตว่าi
ยังคงเปลี่ยนแปลงอยู่แม้ว่าเราจะเปลี่ยนเป็นอาร์กิวเมนต์เริ่มต้นก็ตาม! หากรหัสของคุณกลายพันธุ์ i
คุณจะต้องผูกสำเนาของi
ฟังก์ชันของคุณดังนี้:
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())