ความแตกต่างระหว่าง a - = b และ a = a - b ใน Python


90

ฉันเพิ่งใช้โซลูชันนี้เพื่อหาค่าเฉลี่ยทุกๆ N แถวของเมทริกซ์ แม้ว่าโซลูชันจะใช้งานได้โดยทั่วไปฉันมีปัญหาเมื่อใช้กับอาร์เรย์ 7x1 ฉันสังเกตเห็นว่าปัญหาคือเมื่อใช้ตัว-=ดำเนินการ เพื่อเป็นตัวอย่างเล็ก ๆ :

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]

print a
print b

ซึ่งผลลัพธ์:

[1 1 2]
[1 1 1]

ดังนั้นในกรณีของอาร์เรย์a -= bจะให้ผลลัพธ์ที่แตกต่างจากa = a - b. ฉันคิดจนถึงตอนนี้ว่าสองวิธีนี้เหมือนกันทุกประการ อะไรคือความแตกต่าง?

วิธีการที่ฉันพูดถึงสำหรับการรวมทุก N แถวในเมทริกซ์นั้นใช้งานได้อย่างไรสำหรับเมทริกซ์ 7x4 แต่ไม่ใช่สำหรับอาร์เรย์ 7x1

คำตอบ:


80

หมายเหตุ: การใช้การดำเนินการแบบแทนที่ในอาร์เรย์ NumPy ที่แชร์หน่วยความจำจะไม่มีปัญหาอีกต่อไปในเวอร์ชัน 1.13.0 เป็นต้นไป (ดูรายละเอียดที่นี่ ) การดำเนินการทั้งสองจะให้ผลลัพธ์เหมือนกัน คำตอบนี้ใช้ได้กับ NumPy เวอร์ชันก่อนหน้าเท่านั้น


การกลายพันธุ์อาร์เรย์ในขณะที่ใช้ในการคำนวณอาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิด!

ในตัวอย่างในคำถามลบกับ-=ปรับเปลี่ยนองค์ประกอบที่สองของaและจากนั้นทันทีที่ใช้การปรับเปลี่ยนaองค์ประกอบที่สองในการดำเนินงานในองค์ประกอบที่สามของ

นี่คือสิ่งที่เกิดขึ้นa[1:] -= a[:-1]ทีละขั้นตอน:

  • a[1, 2, 3]เป็นอาร์เรย์ที่มีข้อมูล

  • เรามีสองมุมมองบนข้อมูลนี้a[1:]ถูก[2, 3]และเป็นa[:-1][1, 2]

  • การลบแบบแทนที่-=เริ่มต้น องค์ประกอบแรกของa[:-1], 1, a[1:]ถูกลบออกจากองค์ประกอบแรกของ นี้มีการปรับเปลี่ยนให้เป็นa [1, 1, 3]ตอนนี้เรามีa[1:]มุมมองของข้อมูล[1, 3]และa[:-1]เป็นมุมมองของข้อมูล[1, 1](องค์ประกอบที่สองของอาร์เรย์aได้รับการเปลี่ยนแปลง)

  • a[:-1]คือตอนนี้[1, 1]และตอนนี้NumPy ต้องลบองค์ประกอบที่สองซึ่งเป็น 1 (ไม่ใช่ 2 อีกต่อไป!) ออกจากองค์ประกอบที่สองของa[1:]. สิ่งนี้ทำให้a[1:]เห็นค่า[1, 2]ต่างๆ

  • a[1, 1, 2]คือตอนนี้อาร์เรย์ที่มีค่า

b[1:] = b[1:] - b[:-1]ไม่ได้มีปัญหานี้เพราะb[1:] - b[:-1]สร้างใหม่b[1:]อาร์เรย์แรกและจากนั้นกำหนดค่าในอาร์เรย์นี้ ไม่ได้แก้ไขbตัวเองระหว่างการลบดังนั้นมุมมองb[1:]และb[:-1]ไม่เปลี่ยนแปลง


คำแนะนำทั่วไปคือหลีกเลี่ยงการปรับเปลี่ยนมุมมองหนึ่งแทนที่ด้วยอีกมุมมองหนึ่งหากซ้อนทับกัน ซึ่งรวมถึงผู้ประกอบการ-=, *=ฯลฯ และการใช้outพารามิเตอร์ในฟังก์ชั่นสากล (ชอบnp.subtractและnp.multiply) ในการเขียนกลับไปหนึ่งอาร์เรย์


4
ฉันชอบคำตอบนี้มากกว่าสำหรับคำตอบที่ยอมรับในปัจจุบัน ใช้ภาษาที่ชัดเจนมากเพื่อแสดงผลของการปรับเปลี่ยนวัตถุที่กลายพันธุ์ในสถานที่ ที่สำคัญกว่านั้นย่อหน้าสุดท้ายเน้นโดยตรงถึงความสำคัญของการปรับเปลี่ยนในสถานที่สำหรับมุมมองที่ทับซ้อนกันซึ่งควรเป็นบทเรียนที่จะนำกลับบ้านจากคำถามนี้
Reti43

43

ภายในความแตกต่างคือ:

a[1:] -= a[:-1]

เทียบเท่ากับสิ่งนี้:

a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))

ในขณะนี้:

b[1:] = b[1:] - b[:-1]

แมปกับสิ่งนี้:

b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))

ในบางกรณี__sub__()และ__isub__()ทำงานในลักษณะเดียวกัน แต่วัตถุที่ไม่แน่นอนควรกลายพันธุ์และกลับตัวเองเมื่อใช้ขณะที่พวกเขาควรจะกลับวัตถุใหม่ด้วย__isub__()__sub__()

การใช้การดำเนินการ slice กับวัตถุจำนวนมากจะสร้างมุมมองบนวัตถุดังนั้นการใช้งานจะเข้าถึงหน่วยความจำของวัตถุ "ต้นฉบับ" โดยตรง


11

เอกสารกล่าวว่า:

แนวคิดเบื้องหลังการมอบหมายงานเสริมใน Python ไม่ใช่แค่วิธีที่ง่ายกว่าในการเขียนแนวทางปฏิบัติทั่วไปในการจัดเก็บผลลัพธ์ของการดำเนินการไบนารีในตัวถูกดำเนินการด้านซ้าย แต่ยังเป็นวิธีสำหรับตัวถูกดำเนินการด้านซ้ายที่เป็นปัญหาในการ รู้ว่าควรใช้งาน `` ในตัวเอง '' แทนที่จะสร้างสำเนาที่แก้ไขเอง

ตามกฎหัวแม่มือการลบเติม ( x-=y) เป็นx.__isub__(y)สำหรับในการดำเนินงาน -place ถ้าเป็นไปได้เมื่อปกติ Substraction ( x = x-y) x=x.__sub__(y)จะ บนวัตถุที่ไม่เปลี่ยนแปลงเช่นจำนวนเต็มมันเทียบเท่า แต่สำหรับสิ่งที่เปลี่ยนแปลงไม่ได้เช่นอาร์เรย์หรือรายการดังในตัวอย่างของคุณอาจเป็นสิ่งที่แตกต่างกันมาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.