Mengapa b + = (4,) bekerja dan b = b + (4,) tidak berfungsi ketika b adalah daftar?

77

Jika kami mengambil b = [1,2,3]dan jika kami mencoba melakukan:b+=(4,)

Itu kembali b = [1,2,3,4], tetapi jika kami mencoba melakukannya b = b + (4,)tidak berhasil.

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

Saya berharap b+=(4,)gagal karena Anda tidak dapat menambahkan daftar dan tupel, tetapi berhasil. Jadi saya mencoba b = b + (4,)berharap untuk mendapatkan hasil yang sama, tetapi tidak berhasil.

Supun Dasantha Kuruppu
sumber
4
Saya percaya jawaban dapat ditemukan di sini .
Jochen
Awalnya saya salah membaca ini dan mencoba menutupnya terlalu luas, lalu mencabutnya. Kemudian saya pikir itu harus duplikat, tetapi tidak saja saya tidak dapat memberikan kembali suara, saya mencabut rambut saya berusaha mencari jawaban lain seperti itu. : /
Karl Knechtel
Pertanyaan yang sangat mirip: stackoverflow.com/questions/58048664/…
sanyash

Jawaban:

70

Masalah dengan pertanyaan "mengapa" adalah bahwa biasanya mereka dapat berarti banyak hal yang berbeda. Saya akan mencoba menjawab masing-masing yang saya pikir mungkin ada dalam pikiran Anda.

"Kenapa itu bisa bekerja secara berbeda?" yang dijawab oleh misalnya ini . Pada dasarnya, +=cobalah untuk menggunakan metode objek yang berbeda: __iadd__(yang hanya diperiksa di sisi kiri), vs __add__dan __radd__("reverse add", dicentang di sisi kanan jika sisi kiri tidak memiliki __add__) untuk +.

"Apa tepatnya yang dilakukan setiap versi?" Singkatnya, list.__iadd__metode ini melakukan hal yang sama list.extend(tetapi karena desain bahasa, masih ada tugas kembali).

Ini juga berarti misalnya itu

>>> 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]

+, tentu saja, membuat objek baru, tetapi secara eksplisit membutuhkan daftar lain daripada mencoba menarik elemen dari urutan yang berbeda.

"Mengapa bermanfaat untuk + = untuk melakukan ini? Ini lebih efisien; extendmetode ini tidak harus membuat objek baru. Tentu saja, ini kadang-kadang memiliki beberapa efek mengejutkan (seperti di atas), dan umumnya Python tidak benar-benar tentang efisiensi , tapi keputusan ini dibuat sejak lama.

"Apa alasannya untuk tidak memperbolehkan menambahkan daftar dan tupel dengan +?" Lihat di sini (terima kasih, @ splash58); satu ide adalah bahwa (tuple + list) harus menghasilkan jenis yang sama dengan (list + tuple), dan tidak jelas jenis hasil yang seharusnya. +=tidak memiliki masalah ini, karena a += bjelas tidak boleh mengubah jenis a.

Karl Knechtel
sumber
2
Oof, benar sekali. Dan daftar tidak digunakan |, sehingga agak merusak contoh saya. Jika saya memikirkan contoh yang lebih jelas nanti saya akan menukarnya.
Karl Knechtel
1
Btw |untuk set adalah operator komuter tetapi +untuk list tidak. Untuk alasan itu saya tidak berpikir argumen tentang tipe ambiguitas sangat kuat. Karena operator tidak bepergian mengapa memerlukan jenis yang sama? Orang bisa saja setuju bahwa hasilnya memiliki tipe lhs. Di sisi lain, dengan membatasi list + iterator, pengembang didorong untuk lebih eksplisit tentang niat mereka. Jika Anda ingin membuat daftar baru yang berisi barang-barang dari adiperpanjang oleh barang-barang dari bsudah ada cara untuk melakukan ini: new = a.copy(); new += b. Ini satu garis lagi tapi sangat jelas.
a_guest
Alasan mengapa a += bberperilaku berbeda daripada a = a + bbukan efisiensi. Sebenarnya Guido menganggap perilaku yang dipilih itu kurang membingungkan. Bayangkan sebuah fungsi menerima daftar asebagai argumen dan kemudian melakukan a += [1, 2, 3]. Sintaksis ini jelas terlihat seperti memodifikasi daftar di tempat, daripada membuat daftar baru, sehingga keputusan dibuat bahwa itu harus berperilaku sesuai dengan intuisi kebanyakan orang tentang hasil yang diharapkan. Namun, mekanismenya juga harus bekerja untuk tipe yang tidak berubah seperti ints, yang menyebabkan desain saat ini.
Sven Marnach
Saya pribadi berpikir bahwa desainnya sebenarnya lebih membingungkan daripada sekadar membuat a += bsingkatan a = a + b, seperti yang dilakukan Ruby, tapi saya bisa mengerti bagaimana kami sampai di sana.
Sven Marnach
21

Mereka tidak setara:

b += (4,)

adalah singkatan untuk:

b.extend((4,))

saat +menggabungkan daftar, jadi dengan:

b = b + (4,)

Anda mencoba menyatukan tuple ke daftar

Alfasin
sumber
14

Ketika Anda melakukan ini:

b += (4,)

dikonversi menjadi ini:

b.__iadd__((4,)) 

Di bawah tenda yang dipanggilnya b.extend((4,)), extendterima iterator dan inilah mengapa ini juga berfungsi:

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

tetapi ketika Anda melakukan ini:

b = b + (4,)

dikonversi menjadi ini:

b = b.__add__((4,)) 

hanya menerima objek daftar.

Charif DZ
sumber
4

Dari dokumen resmi, untuk jenis urutan yang dapat berubah, keduanya:

s += t
s.extend(t)

didefinisikan sebagai:

meluas sdengan isit

Yang berbeda dari yang didefinisikan sebagai:

s = s + t    # not equivalent in Python!

Ini juga berarti semua jenis urutan akan berfungsit , termasuk tupel seperti pada contoh Anda.

Tapi itu juga berfungsi untuk rentang dan generator! Misalnya, Anda juga dapat melakukan:

s += range(3)
Biji pohon ek
sumber
3

Operator penugasan "augmented" seperti +=diperkenalkan dalam Python 2.0, yang dirilis pada Oktober 2000. Desain dan alasannya dijelaskan dalam PEP 203 . Salah satu tujuan yang dinyatakan dari operator ini adalah dukungan operasi di tempat. Penulisan

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

seharusnya memperbarui daftar a di tempat . Ini penting jika ada referensi lain ke daftar a, misalnya ketika aditerima sebagai argumen fungsi.

Namun, operasi tidak selalu dapat terjadi di tempat, karena banyak tipe Python, termasuk bilangan bulat dan string, tidak dapat diubah , jadi misalnya i += 1untuk bilangan bulat itidak mungkin beroperasi di tempat.

Singkatnya, operator penugasan augmented seharusnya bekerja di tempat bila memungkinkan, dan membuat objek baru sebaliknya. Untuk memfasilitasi sasaran desain ini, ekspresi x += yditentukan untuk berperilaku sebagai berikut:

  • Jika x.__iadd__didefinisikan, x.__iadd__(y)dievaluasi.
  • Kalau tidak, jika x.__add__diterapkan x.__add__(y)dievaluasi.
  • Kalau tidak, jika y.__radd__diterapkan y.__radd__(x)dievaluasi.
  • Kalau tidak, salahkan.

Hasil pertama yang diperoleh oleh proses ini akan ditugaskan kembali x(kecuali jika hasil itu adalah NotImplementedsingleton, dalam hal ini pencarian dilanjutkan dengan langkah berikutnya).

Proses ini memungkinkan jenis yang mendukung modifikasi di tempat untuk menerapkan __iadd__(). Jenis yang tidak mendukung modifikasi di tempat tidak perlu menambahkan metode sihir baru, karena Python akan secara otomatis kembali ke dasarnya x = x + y.

Jadi mari kita akhirnya datang ke pertanyaan Anda yang sebenarnya - mengapa Anda dapat menambahkan tuple ke daftar dengan operator penugasan augmented Dari memori, sejarah ini kira-kira seperti ini: list.__iadd__()Metode ini diterapkan untuk memanggil metode yang sudah ada list.extend()di Python 2.0. Ketika iterator diperkenalkan dengan Python 2.1, list.extend()metode ini diperbarui untuk menerima iterator yang sewenang-wenang. Hasil akhir dari perubahan ini adalah yang my_list += my_tuplebekerja mulai dari Python 2.1. The list.__add__()metode, bagaimanapun, tidak pernah seharusnya mendukung iterator sewenang-wenang sebagai argumen kanan - ini dianggap tidak pantas untuk bahasa sangat diketik.

Saya pribadi berpikir implementasi dari operator augmented menjadi agak terlalu rumit dalam Python. Ini memiliki banyak efek samping yang mengejutkan, misalnya kode ini:

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

Baris kedua memunculkan TypeError: 'tuple' object does not support item assignment, tetapi operasi tetap berhasil dilakukan - takan ([42, 44], [43])setelah mengeksekusi baris yang memunculkan kesalahan.

Sven Marnach
sumber
Bravo! Memiliki referensi ke PEP sangat berguna. Saya menambahkan tautan di ujung yang lain, ke pertanyaan SO sebelumnya tentang perilaku list-in-tuple. Ketika saya melihat kembali seperti apa Python sebelum 2.3 atau lebih, sepertinya praktis tidak dapat dibandingkan dengan hari ini ... (dan saya masih memiliki memori samar-samar untuk mencoba dan gagal mendapatkan 1,5 untuk melakukan sesuatu yang berguna pada Mac yang sangat tua)
Karl Knechtel
2

Kebanyakan orang akan mengharapkan X + = Y setara dengan X = X + Y. Memang, Referensi Saku Python (edisi ke-4) oleh Mark Lutz mengatakan pada halaman 57 "Dua format berikut kira-kira setara: X = X + Y, X + = Y ". Namun, orang-orang yang menentukan Python tidak membuatnya setara. Mungkin itu adalah kesalahan yang akan menghasilkan berjam-jam waktu debugging oleh programmer frustrasi selama Python tetap digunakan, tetapi sekarang hanya seperti itu Python. Jika X adalah tipe urutan yang bisa berubah, X + = Y setara dengan X.extend (Y) dan bukan X = X + Y.

zizzler
sumber
> Mungkin itu adalah kesalahan yang akan menghasilkan berjam-jam waktu debugging oleh programmer frustrasi selama Python tetap digunakan <- apakah Anda benar-benar menderita karena ini? Anda sepertinya berbicara dari pengalaman. Saya sangat ingin mendengar cerita Anda.
Veky
1

Seperti yang dijelaskan di sini , jika arraytidak menerapkan __iadd__metode, b+=(4,)itu hanya singkatan b = b + (4,)tetapi jelas tidak, begitu arrayjuga __iadd__metode implement . Rupanya implementasi __iadd__metode adalah seperti ini:

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

Namun kita tahu bahwa kode di atas bukanlah implementasi __iadd__metode yang sebenarnya tetapi kita dapat mengasumsikan dan menerima bahwa ada sesuatu seperti extendmetode, yang menerima tuppleinput.

Hamidreza
sumber