ผมก็บอกว่าสามารถมีผลกระทบที่แตกต่างกันกว่าสัญกรณ์มาตรฐาน+= i = i +มีกรณีที่i += 1จะแตกต่างจากi = i + 1?
i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6] Trueนักพัฒนาหลายคนอาจไม่สังเกตว่าid(i)การเปลี่ยนแปลงสำหรับการดำเนินการหนึ่ง แต่ไม่เปลี่ยน
ผมก็บอกว่าสามารถมีผลกระทบที่แตกต่างกันกว่าสัญกรณ์มาตรฐาน+= i = i +มีกรณีที่i += 1จะแตกต่างจากi = i + 1?
i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6] Trueนักพัฒนาหลายคนอาจไม่สังเกตว่าid(i)การเปลี่ยนแปลงสำหรับการดำเนินการหนึ่ง แต่ไม่เปลี่ยน
คำตอบ:
ขึ้นอยู่กับวัตถุiทั้งหมด
+=เรียก__iadd__วิธีการ (ถ้ามี - ตกกลับบน__add__ถ้ามันไม่ได้อยู่) ในขณะที่+โทร__add__วิธีที่ 1หรือ__radd__วิธีการในไม่กี่กรณีที่ 2
จากเปอร์สเปคทีฟ API __iadd__ควรใช้สำหรับการแก้ไขออบเจ็กต์ที่ไม่แน่นอนในสถานที่ (ส่งคืนออบเจ็กต์ที่กลายพันธุ์) ในขณะที่__add__ควรส่งคืนอินสแตนซ์ใหม่ของบางสิ่ง สำหรับออบเจ็กต์ที่ไม่เปลี่ยนรูปแบบทั้งสองเมธอดจะส่งคืนอินสแตนซ์ใหม่ แต่__iadd__จะวางอินสแตนซ์ใหม่ในเนมสเปซปัจจุบันด้วยชื่อเดียวกันกับที่อินสแตนซ์เก่ามี นี่คือเหตุผล
i = 1
i += 1
iดูเหมือนว่าจะเพิ่มขึ้น ในความเป็นจริงคุณจะได้รับจำนวนเต็มใหม่และกำหนดให้ "ด้านบนของ" i- สูญเสียการอ้างอิงเดียวกับจำนวนเต็มเก่า ในกรณีนี้เป็นเหมือนกับi += 1 i = i + 1แต่ด้วยวัตถุที่ไม่แน่นอนส่วนใหญ่มันเป็นเรื่องที่แตกต่าง:
เป็นตัวอย่างที่เป็นรูปธรรม:
a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
เปรียบเทียบกับ:
a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
แจ้งให้ทราบว่าในตัวอย่างแรกตั้งแต่bและaอ้างอิงวัตถุเดียวกันเมื่อฉันใช้+=ในbก็จริงการเปลี่ยนแปลงb(และaเห็นการเปลี่ยนแปลงที่มากเกินไป - หลังจากที่ทุกคนก็อ้างอิงรายการเดียวกัน) ในกรณีที่สอง แต่เมื่อฉันทำb = b + [1, 2, 3]นี้จะใช้เวลารายการที่มีการอ้างอิงและเชื่อมกับรายการใหม่b [1, 2, 3]จากนั้นจะเก็บรายการที่ตัดแบ่งในเนมสเปซปัจจุบันเป็นb- โดยไม่คำนึงถึงสิ่งที่bเป็นบรรทัดก่อน
1ในนิพจน์x + yหากx.__add__ไม่ได้ใช้งานหรือหากx.__add__(y)ส่งคืนNotImplemented และ xและyมีประเภทที่แตกต่างกันแล้วx + yy.__radd__(x)พยายามที่จะเรียกร้อง ดังนั้นในกรณีที่คุณมี
foo_instance += bar_instance
หากFooไม่ได้ใช้งาน__add__หรือ__iadd__ผลลัพธ์ที่นี่เหมือนกับ
foo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2ในการแสดงออกfoo_instance + bar_instance, bar_instance.__radd__จะพยายามก่อนfoo_instance.__add__ ถ้าประเภทของการbar_instanceเป็น subclass ของประเภทของfoo_instance(เช่นissubclass(Bar, Foo)) เหตุผลสำหรับสิ่งนี้เป็นเพราะBarในบางแง่วัตถุ "ระดับสูง" กว่าFooนั้นBarควรได้รับตัวเลือกในการเอาชนะFooพฤติกรรมของ
+=เรียก__iadd__ ว่ามันมีอยู่และตกกลับไปที่การเพิ่มและ rebinding มิฉะนั้น นั่นเป็นเหตุผลที่การทำงานแม้ว่าจะไม่มีi = 1; i += 1 int.__iadd__แต่นอกจากคำอธิบายเล็กน้อยแล้ว
int.__iadd__ __add__ฉันดีใจที่ได้เรียนรู้สิ่งใหม่วันนี้ :)
x + yสายy.__radd__(x)ถ้าx.__add__ไม่ได้อยู่ (หรือผลตอบแทนNotImplementedและxและyเป็นประเภทที่แตกต่างกัน)
nb_inplace_addหรือsq_inplace_concatและฟังก์ชั่น C API เหล่านั้นมีข้อกำหนดที่เข้มงวดกว่าวิธี Python dunder และ… แต่ฉันไม่คิดว่ามันเกี่ยวข้องกับคำตอบ ความแตกต่างหลักคือ+=พยายามเพิ่มในสถานที่ก่อนที่จะกลับไปทำหน้าที่เหมือน+ที่ฉันคิดว่าคุณได้อธิบายแล้ว
ภายใต้ฝาครอบi += 1ทำสิ่งนี้:
try:
i = i.__iadd__(1)
except AttributeError:
i = i.__add__(1)
ในขณะที่i = i + 1ทำสิ่งนี้:
i = i.__add__(1)
นี่คือการทำให้ใหญ่เกินไปเล็กน้อย แต่คุณจะได้รับแนวคิด: Python ให้ประเภทของวิธีการจัดการ+=เป็นพิเศษโดยการสร้าง__iadd__วิธีการและ__add__วิธีการเช่นเดียวกับ
ความตั้งใจก็คือประเภทที่เปลี่ยนแปลงไม่ได้เช่นlistจะกลายพันธุ์ในตัวเอง__iadd__(แล้วกลับมาselfเว้นแต่ว่าคุณกำลังทำอะไรที่ยุ่งยากมาก) ในขณะที่ประเภทที่ไม่เปลี่ยนรูปเช่นintจะไม่ใช้มัน
ตัวอย่างเช่น:
>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]
เพราะl2เป็นวัตถุเดียวกับl1และคุณกลายพันธุ์คุณยังกลายพันธุ์l1l2
แต่:
>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]
ที่นี่คุณไม่ได้กลายพันธุ์l1; แต่คุณสร้างรายการใหม่l1 + [3]และรีบาวด์ชื่อl1ให้l2ชี้ไปที่เดิมโดยชี้ไปที่รายการเดิม
(ใน+=เวอร์ชั่นนี้คุณกำลังทำการ rebinding l1มันก็เป็นเช่นนั้นในกรณีที่คุณกำลัง rebinding มันให้เป็นแบบเดียวกันกับที่listมีอยู่แล้วดังนั้นคุณมักจะไม่สนใจส่วนนั้น)
__iadd__โทรจริง__add__ในกรณีของAttributeError?
i.__iadd__ไม่โทร__add__; มันเป็นสายที่i += 1 __add__
i = i.__iadd__(1)- iadd สามารถปรับเปลี่ยนวัตถุในสถานที่ แต่ไม่จำเป็นต้องและคาดว่าจะส่งกลับผลลัพธ์ในทั้งสองกรณี
operator.iaddการโทร__add__บนAttributeErrorแต่ก็ไม่สามารถ rebind ผล ... ดังนั้นi=1; operator.iadd(i, 1)ผลตอบแทนที่ 2 และใบตั้งค่าให้i 1ซึ่งค่อนข้างสับสน
นี่คือตัวอย่างที่เปรียบเทียบโดยตรงi += xกับi = i + x:
def foo(x):
x = x + [42]
def bar(x):
x += [42]
c = [27]
foo(c); # c is not changed
bar(c); # c is changed to [27, 42]
+=ทำหน้าที่เหมือนextend()ในกรณีของรายการ