ผมก็บอกว่าสามารถมีผลกระทบที่แตกต่างกันกว่าสัญกรณ์มาตรฐาน+=
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 + y
y.__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
และคุณกลายพันธุ์คุณยังกลายพันธุ์l1
l2
แต่:
>>> 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()
ในกรณีของรายการ