เหตุใด b + = (4,) ทำงานและ b = b + (4,) ไม่ทำงานเมื่อ b เป็นรายการ


77

ถ้าเราใช้b = [1,2,3]และถ้าเราลองทำ:b+=(4,)

มันกลับb = [1,2,3,4]มา แต่ถ้าเราลองทำb = b + (4,)มันไม่ทำงาน

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

ฉันคาดว่าb+=(4,)จะล้มเหลวเนื่องจากคุณไม่สามารถเพิ่มรายการและสิ่งอันดับ แต่มันใช้งานได้ ดังนั้นฉันจึงb = b + (4,)คาดหวังว่าจะได้รับผลลัพธ์ที่เหมือนกัน แต่ก็ไม่ได้ผล


4
ผมเชื่อว่าคำตอบที่สามารถพบได้ที่นี่
jochen


ตอนแรกฉันอ่านผิดและพยายามที่จะปิดมันให้กว้างเกินไป จากนั้นฉันคิดว่ามันจะต้องซ้ำกัน แต่ไม่เพียง แต่ฉันจะไม่สามารถลงคะแนนอีกครั้งฉันดึงผมออกไปพยายามหาคำตอบอื่น ๆ เช่นนั้น : /
Karl Knechtel

คำถามที่คล้ายกันมาก: stackoverflow.com/questions/58048664/…
sanyash

คำตอบ:


70

ปัญหาเกี่ยวกับคำถาม "ทำไม" คือโดยปกติแล้วพวกเขาสามารถหมายถึงหลายสิ่งที่แตกต่างกัน ฉันจะพยายามตอบแต่ละข้อที่ฉันคิดว่าคุณอาจมีในใจ

"ทำไมจึงเป็นไปได้ที่จะทำงานแตกต่างกัน?" ซึ่งเป็นคำตอบโดยการเช่นนี้ โดยทั่วไปแล้ว+=พยายามใช้วิธีการต่าง ๆ ของวัตถุ: __iadd__(ซึ่งตรวจสอบทางด้านซ้ายมือเท่านั้น), vs __add__และ__radd__("reverse add", ตรวจสอบทางด้านขวาหากไม่มีด้านซ้าย__add__) +สำหรับ

"แต่ละรุ่นทำหน้าที่อะไรกันแน่" ในระยะสั้นlist.__iadd__วิธีการทำเช่นเดียวกับlist.extend(แต่เนื่องจากการออกแบบภาษายังมีการมอบหมายกลับ)

นี่ก็หมายถึงตัวอย่างเช่น

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+แน่นอนสร้างวัตถุใหม่ แต่ต้องการรายการอื่นอย่างชัดเจนแทนที่จะพยายามดึงองค์ประกอบออกจากลำดับที่แตกต่างกัน

"ทำไมจึงมีประโยชน์สำหรับ + ​​= ในการทำเช่นนี้มันมีประสิทธิภาพมากกว่าextendวิธีนี้ไม่จำเป็นต้องสร้างวัตถุใหม่แน่นอนว่าบางครั้งมีเอฟเฟกต์ที่น่าประหลาดใจบางครั้ง (เช่นด้านบน) และโดยทั่วไปแล้ว Python ไม่ได้เกี่ยวกับประสิทธิภาพ แต่การตัดสินใจเหล่านี้ทำมานานแล้ว

"สาเหตุที่ไม่อนุญาตให้เพิ่มรายการและสิ่งอันดับด้วย + คืออะไร" ดูที่นี่ (ขอบคุณ @ splash58); แนวคิดหนึ่งคือ (รายการ tuple +) ควรสร้างประเภทเดียวกันกับ (รายการ + tuple) และยังไม่ชัดเจนว่าควรจะพิมพ์ผลลัพธ์ประเภทใด +=ไม่ได้มีปัญหานี้เพราะเห็นได้ชัดว่าไม่ควรเปลี่ยนชนิดของa += ba


2
Oof ค่อนข้างถูกต้อง และรายการไม่ได้ใช้|ดังนั้นตัวอย่างของฉันก็พัง หากฉันนึกถึงตัวอย่างที่ชัดเจนในภายหลังฉันจะสลับมัน
Karl Knechtel

1
Btw |สำหรับเซตเป็นตัวดำเนินการเดินทาง แต่ไม่ใช่+รายการ ด้วยเหตุนี้ฉันไม่คิดว่าข้อโต้แย้งเกี่ยวกับความคลุมเครือประเภทนั้นแข็งแกร่งเป็นพิเศษ เนื่องจากโอเปอเรเตอร์ไม่เปลี่ยนทำไมต้องใช้ชนิดเดียวกัน เราสามารถตกลงกันได้ว่าผลลัพธ์มีประเภทของ lhs ในทางตรงกันข้ามโดยการ จำกัดlist + iteratorผู้พัฒนาได้รับการสนับสนุนให้ชัดเจนมากขึ้นเกี่ยวกับความตั้งใจของพวกเขา หากคุณต้องการสร้างรายการใหม่ที่มีสิ่งที่ได้จากการaขยายโดยสิ่งที่ได้จากการมีอยู่แล้ววิธีการทำเช่นนี้:b new = a.copy(); new += bเป็นอีกหนึ่งบรรทัด แต่ใส
a_guest

เหตุผลที่a += bทำงานแตกต่างจากa = a + bไม่มีประสิทธิภาพ ที่จริงแล้วกุยโด้ถือว่าพฤติกรรมที่เลือกนั้นสร้างความสับสนน้อยลง ลองนึกภาพฟังก์ชั่นที่ได้รับรายการเป็นอาร์กิวเมนต์แล้วทำa a += [1, 2, 3]ไวยากรณ์นี้ดูเหมือนว่าจะแก้ไขรายการในสถานที่แทนที่จะสร้างรายการใหม่ดังนั้นการตัดสินใจจึงควรทำตามสิ่งที่สัญชาตญาณของคนส่วนใหญ่เกี่ยวกับผลลัพธ์ที่คาดหวัง อย่างไรก็ตามกลไกก็ต้องทำงานกับประเภทที่ไม่เปลี่ยนรูปแบบเช่นints ซึ่งนำไปสู่การออกแบบในปัจจุบัน
Sven Marnach

ผมเองคิดว่าการออกแบบที่เป็นจริงมากขึ้นทำให้เกิดความสับสนมากกว่าเพียงแค่การทำa += bชวเลขสำหรับa = a + bเป็นทับทิมได้ แต่ฉันสามารถเข้าใจวิธีการที่เราไปถึงที่นั่น
Sven Marnach

21

พวกเขาจะไม่เทียบเท่า:

b += (4,)

จดชวเลข

b.extend((4,))

ในขณะที่+เชื่อมรายชื่อเข้าด้วยกันดังนั้นโดย:

b = b + (4,)

คุณกำลังพยายามเชื่อมต่อ tuple กับรายการ


14

เมื่อคุณทำสิ่งนี้:

b += (4,)

ถูกแปลงเป็นนี้:

b.__iadd__((4,)) 

ภายใต้ฝาครอบที่มีการเรียกใช้b.extend((4,))ให้extendยอมรับตัววนซ้ำและนี่คือสาเหตุว่าทำไมจึงใช้งานได้:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

แต่เมื่อคุณทำสิ่งนี้:

b = b + (4,)

ถูกแปลงเป็นนี้:

b = b.__add__((4,)) 

ยอมรับวัตถุรายการเท่านั้น


4

จากเอกสารอย่างเป็นทางการสำหรับประเภทลำดับที่ไม่แน่นอนทั้งสอง:

s += t
s.extend(t)

ถูกกำหนดเป็น:

ขยายsเนื้อหาของt

ซึ่งแตกต่างจากการถูกกำหนดเป็น:

s = s + t    # not equivalent in Python!

นอกจากนี้ยังหมายถึงประเภทลำดับใด ๆ ที่ใช้งานได้tรวมถึงสิ่งอันดับในตัวอย่างของคุณ

แต่มันก็ใช้ได้กับพิสัยและกำเนิดด้วย! ตัวอย่างเช่นคุณยังสามารถ:

s += range(3)

3

"การเติม" ผู้ประกอบการที่ได้รับมอบหมายเช่น+=ถูกนำมาใช้ในหลาม 2.0 ซึ่งได้รับการปล่อยตัวในเดือนตุลาคม 2000 การออกแบบและเหตุผลที่อธิบายไว้ในPEP 203 หนึ่งในเป้าหมายที่ประกาศไว้ของผู้ประกอบการเหล่านี้คือการสนับสนุนการดำเนินงานในสถานที่ การเขียน

a = [1, 2, 3]
a += [4, 5, 6]

ควรจะปรับปรุงรายการในสถานที่a สิ่งนี้สำคัญหากมีการอ้างอิงอื่น ๆ ในรายการaเช่นเมื่อaได้รับเป็นอาร์กิวเมนต์ของฟังก์ชัน

อย่างไรก็ตามการดำเนินการไม่สามารถเกิดขึ้นได้เสมอเนื่องจาก Python หลายประเภทรวมถึงจำนวนเต็มและสตริงไม่สามารถเปลี่ยนแปลงได้ดังนั้นi += 1สำหรับจำนวนเต็มiจึงไม่สามารถใช้งานได้

โดยสรุปผู้ประกอบการที่ได้รับมอบหมายเพิ่มเติมควรจะทำงานในสถานที่เมื่อเป็นไปได้และสร้างวัตถุใหม่เป็นอย่างอื่น เพื่ออำนวยความสะดวกในเป้าหมายการออกแบบเหล่านี้นิพจน์x += yถูกระบุให้ทำงานดังนี้:

  • หากx.__iadd__ถูกกำหนดx.__iadd__(y)จะถูกประเมิน
  • มิฉะนั้นหากx.__add__มีการใช้งานx.__add__(y)จะถูกประเมิน
  • มิฉะนั้นหากy.__radd__มีการใช้งานy.__radd__(x)จะถูกประเมิน
  • มิฉะนั้นยกข้อผิดพลาด

ผลลัพธ์แรกที่ได้จากกระบวนการนี้จะถูกกำหนดกลับไปที่x(เว้นแต่ว่าผลลัพธ์จะเป็นNotImplementedซิงเกิลซึ่งในกรณีนี้การค้นหาจะดำเนินการต่อในขั้นตอนถัดไป)

__iadd__()กระบวนการนี้จะช่วยให้การปรับเปลี่ยนประเภทที่สนับสนุนในสถานที่ที่จะใช้ ประเภทที่ไม่สนับสนุนในสถานที่การปรับเปลี่ยนไม่จำเป็นต้องเพิ่มวิธีมายากลใหม่ ๆ x = x + yตั้งแต่ปีงูใหญ่จะตกอยู่โดยอัตโนมัติกลับไปเป็นหลัก

ดังนั้นในที่สุดเรามาถึงคำถามที่แท้จริงของคุณ - ทำไมคุณสามารถเพิ่ม tuple ลงในรายการด้วยตัวดำเนินการมอบหมายเพิ่มเติม จากความทรงจำประวัติความเป็นมาของมันก็เป็นแบบนี้: list.__iadd__()วิธีการนี้ถูกใช้เพื่อเรียกlist.extend()วิธีที่มีอยู่แล้วใน Python 2.0 เมื่อมีการแนะนำตัววนซ้ำใน Python 2.1 list.extend()วิธีนี้ได้รับการปรับปรุงเพื่อยอมรับตัววนซ้ำโดยพลการ ผลลัพธ์สุดท้ายของการเปลี่ยนแปลงเหล่านี้คือการmy_list += my_tupleทำงานที่เริ่มต้นจาก Python 2.1 อย่างไรก็ตามlist.__add__()วิธีนี้ไม่ควรสนับสนุนการทำซ้ำโดยพลการในฐานะที่เป็นอาร์กิวเมนต์ทางด้านขวา - ซึ่งถือว่าไม่เหมาะสมกับภาษาที่พิมพ์

โดยส่วนตัวแล้วฉันคิดว่าการใช้งานตัวดำเนินการเติมสิ้นสุดลงด้วยความซับซ้อนเกินไปใน Python มันมีผลข้างเคียงที่น่าแปลกใจมากมายเช่นรหัสนี้:

t = ([42], [43])
t[0] += [44]

บรรทัดที่สองยกTypeError: 'tuple' object does not support item assignment, แต่การดำเนินการจะดำเนินการประสบความสำเร็จอยู่แล้ว - tจะเป็น([42, 44], [43])หลังจากรันบรรทัดที่ก่อให้เกิดข้อผิดพลาด


ไชโย! การมีการอ้างอิงถึง PEP นั้นมีประโยชน์อย่างยิ่ง ฉันเพิ่มลิงก์ที่ปลายอีกด้านหนึ่งเป็นคำถาม SO ก่อนหน้านี้เกี่ยวกับพฤติกรรม list-in-tuple เมื่อผมมองย้อนกลับไปในสิ่งที่หลามเป็นเหมือนก่อน 2.3 หรือเพื่อให้ดูเหมือนว่าใช้ไม่ได้จริงเมื่อเทียบกับวันนี้ ... (และฉันยังคงมีความทรงจำที่คลุมเครือของความพยายามและความล้มเหลวที่จะได้รับ 1.5 ที่จะทำอะไรที่เป็นประโยชน์บน Mac เก่ามาก)
Karl Knechtel

2

คนส่วนใหญ่คาดหวังว่า X + = Y จะเทียบเท่ากับ X = X + Y แน่นอนการอ้างอิงกระเป๋า Python (4 ed) โดย Mark Lutz กล่าวในหน้า 57 "รูปแบบสองรูปแบบต่อไปนี้เทียบเท่ากันโดยประมาณ: X = X + Y, X + = Y " อย่างไรก็ตามคนที่ระบุ Python ไม่ได้ทำให้พวกเขาเทียบเท่า อาจเป็นความผิดพลาดซึ่งจะส่งผลให้เวลาในการดีบักเป็นชั่วโมงโดยโปรแกรมเมอร์ที่ไม่พอใจตราบใดที่ Python ยังคงใช้งานอยู่ แต่ตอนนี้เป็นเพียงวิธีที่ Python ใช้ ถ้า X เป็นชนิดลำดับที่ผันแปรได้ X + = Y เทียบเท่ากับ X.extend (Y) และไม่เท่ากับ X = X + Y


> อาจเป็นความผิดพลาดซึ่งจะส่งผลให้เวลาในการดีบักเป็นชั่วโมงโดยโปรแกรมเมอร์ที่ไม่พอใจตราบใดที่ Python ยังคงใช้งานอยู่ <- คุณต้องทนทุกข์เพราะเหตุนี้หรือไม่? คุณดูเหมือนจะพูดจากประสบการณ์ ฉันอยากได้ยินเรื่องราวของคุณมาก
Veky

1

ตามที่อธิบายไว้ที่นี่หากarrayไม่ได้ใช้__iadd__วิธีการนั้นb+=(4,)จะเป็นเพียงแค่การจดชวเลขb = b + (4,)แต่เห็นได้ชัดว่าไม่ใช่วิธีการarrayใช้งาน __iadd__เห็นได้ชัดว่าการดำเนินการของ__iadd__วิธีการเป็นดังนี้:

def __iadd__(self, x):
    self.extend(x)

อย่างไรก็ตามเรารู้ว่าโค้ดข้างต้นไม่ใช่วิธีการใช้งานจริง__iadd__แต่เราสามารถสันนิษฐานได้และยอมรับว่ามีบางอย่างเช่นextendวิธีการซึ่งรับtuppleอินพุต

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