@AshwiniChaudhary Itu perbedaan yang cukup halus, mengingat i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]ini True. Banyak pengembang mungkin tidak memperhatikan bahwa id(i)perubahan untuk satu operasi, tetapi tidak yang lain.
kojiro
1
@ Kojiro - Meskipun ini adalah perbedaan yang halus, saya pikir ini adalah yang penting.
mgilson
@ Mcgson itu penting, jadi saya merasa perlu penjelasan. :)
Dari perspektif API, __iadd__seharusnya digunakan untuk memodifikasi objek yang bisa berubah di tempat (mengembalikan objek yang dimutasi) sedangkan __add__seharusnya mengembalikan contoh baru dari sesuatu. Untuk objek yang tidak dapat diubah , kedua metode mengembalikan instance baru, tetapi __iadd__akan menempatkan instance baru di namespace saat ini dengan nama yang sama dengan instance lama. Ini sebabnya
i =1
i +=1
tampaknya meningkat i. Pada kenyataannya, Anda mendapatkan integer baru dan menetapkannya "di atas" i- kehilangan satu referensi ke integer lama. Dalam hal ini, i += 1persis sama dengan i = i + 1. Tapi, dengan sebagian besar objek yang bisa berubah, ini adalah cerita yang berbeda:
Sebagai contoh nyata:
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]
dibandingkan dengan:
a =[1,2,3]
b = a
b = b +[1,2,3]print a #[1, 2, 3]print b #[1, 2, 3, 1, 2, 3]
pemberitahuan bagaimana dalam contoh pertama, karena bdan areferensi objek yang sama, ketika saya menggunakan +=pada b, itu benar-benar berubah b(dan amelihat perubahan itu juga - Setelah semua, itu referensi daftar yang sama). Namun dalam kasus kedua, ketika saya melakukannya b = b + [1, 2, 3], ini mengambil daftar yang bmereferensikan dan menyatukannya dengan daftar baru [1, 2, 3]. Itu kemudian menyimpan daftar gabungan di namespace saat ini sebagai b- Tanpa memperhatikan apa bbaris sebelumnya.
1 Dalam ekspresi x + y, jika x.__add__tidak diimplementasikan atau jika x.__add__(y)kembali NotImplementeddan xdan ymemiliki jenis yang berbeda , maka x + ycobalah menelepon y.__radd__(x). Jadi, dalam kasus di mana Anda miliki
foo_instance += bar_instance
jika Footidak menerapkan __add__atau __iadd__hasilnya di sini sama dengan
2 Dalam ekspresi foo_instance + bar_instance, bar_instance.__radd__akan dicoba sebelumnya foo_instance.__add__jika jenisnya bar_instanceadalah subkelas dari jenis foo_instance(misalnya issubclass(Bar, Foo)). Rasional untuk ini adalah karena Bardalam beberapa arti "-tingkat yang lebih tinggi" objek dari Foosehingga Barharus mendapatkan pilihan utama Fooperilaku 's.
Nah, +=panggilan __iadd__jika ada , dan kembali ke menambah dan membatalkan sebaliknya. Itu sebabnya i = 1; i += 1bekerja meskipun tidak ada int.__iadd__. Tapi selain nit kecil itu, penjelasannya bagus sekali.
abarnert
4
@abarnert - Saya selalu berasumsi bahwa int.__iadd__baru saja menelepon __add__. Saya senang telah mempelajari sesuatu yang baru hari ini :).
mgilson
@abarnert - Saya kira mungkin harus lengkap , x + ypanggilan y.__radd__(x)jika x.__add__tidak ada (atau kembali NotImplementeddan xdan ydari jenis yang berbeda)
mgilson
Jika Anda benar-benar ingin menjadi pelengkap, Anda harus menyebutkan bahwa bit "jika ada" melewati mekanisme getattr yang biasa, kecuali untuk beberapa quirks dengan kelas klasik, dan untuk tipe yang diimplementasikan dalam C API ia malah mencari baik nb_inplace_addatau sq_inplace_concat, dan fungsi-fungsi C API memiliki persyaratan yang lebih ketat daripada metode Python dunder, dan ... Tapi saya tidak berpikir itu relevan dengan jawabannya. Perbedaan utamanya adalah +=mencoba melakukan penambahan di tempat sebelum kembali bertindak seperti +, yang menurut saya sudah Anda jelaskan.
abarnert
Ya, saya kira Anda benar ... Meskipun saya bisa saja kembali pada pendirian bahwa C API bukan bagian dari python . Itu bagian dari Cpython :-P
mgilson
67
Di balik selimut, i += 1lakukan sesuatu seperti ini:
try:
i = i.__iadd__(1)exceptAttributeError:
i = i.__add__(1)
Sementara i = i + 1melakukan sesuatu seperti ini:
i = i.__add__(1)
Ini adalah penyederhanaan yang sedikit berlebihan, tetapi Anda mendapatkan ide: Python memberikan tipe cara untuk menangani +=secara khusus, dengan menciptakan __iadd__metode dan juga __add__.
Tujuannya adalah bahwa tipe yang dapat berubah, seperti list, akan bermutasi di __iadd__(dan kemudian kembali self, kecuali jika Anda melakukan sesuatu yang sangat rumit), sedangkan tipe yang tidak dapat diubah, seperti int, tidak akan mengimplementasikannya.
Sebagai contoh:
>>> l1 =[]>>> l2 = l1
>>> l1 +=[3]>>> l2
[3]
Karena l2objeknya sama dengan l1, dan Anda bermutasi l1, Anda juga bermutasi l2.
Tapi:
>>> l1 =[]>>> l2 = l1
>>> l1 = l1 +[3]>>> l2
[]
Di sini, Anda tidak bermutasi l1; alih-alih, Anda membuat daftar baru l1 + [3],, dan memunculkan kembali nama l1untuk menunjuk padanya, meninggalkan l2menunjuk pada daftar asli.
(Dalam +=versi itu, kamu juga memberontak l1, hanya saja dalam kasus itu kamu listmengikatnya dengan yang sudah terikat, jadi kamu biasanya bisa mengabaikan bagian itu.)
tidak __iadd__benar - benar memanggil __add__jika ada AttributeError?
mgilson
Yah, i.__iadd__jangan menelepon __add__; itu i += 1yang memanggil __add__.
abarnert
errr ... Ya, itu yang saya maksud. Menarik. Saya tidak menyadari bahwa itu dilakukan secara otomatis.
mgilson
3
Upaya pertama sebenarnya i = i.__iadd__(1)- iadddapat memodifikasi objek di tempat, tetapi tidak harus, dan diharapkan mengembalikan hasilnya dalam kedua kasus.
lvc
Perhatikan bahwa ini berarti operator.iaddpanggilan __add__aktif AttributeError, tetapi tidak dapat mengulang hasilnya ... jadi i=1; operator.iadd(i, 1)kembalikan 2 dan isetel ke 1. Yang agak membingungkan.
abarnert
6
Berikut adalah contoh yang secara langsung dibandingkan i += xdengan 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]
+=
bertindak sepertiextend()
dalam kasus daftar.i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
iniTrue
. Banyak pengembang mungkin tidak memperhatikan bahwaid(i)
perubahan untuk satu operasi, tetapi tidak yang lain.Jawaban:
Ini sepenuhnya tergantung pada objek
i
.+=
memanggil__iadd__
metode (jika ada - mundur kembali__add__
jika tidak ada) sedangkan+
memanggil__add__
metode 1 atau__radd__
metode dalam beberapa kasus 2 .Dari perspektif API,
__iadd__
seharusnya digunakan untuk memodifikasi objek yang bisa berubah di tempat (mengembalikan objek yang dimutasi) sedangkan__add__
seharusnya mengembalikan contoh baru dari sesuatu. Untuk objek yang tidak dapat diubah , kedua metode mengembalikan instance baru, tetapi__iadd__
akan menempatkan instance baru di namespace saat ini dengan nama yang sama dengan instance lama. Ini sebabnyatampaknya meningkat
i
. Pada kenyataannya, Anda mendapatkan integer baru dan menetapkannya "di atas"i
- kehilangan satu referensi ke integer lama. Dalam hal ini,i += 1
persis sama dengani = i + 1
. Tapi, dengan sebagian besar objek yang bisa berubah, ini adalah cerita yang berbeda:Sebagai contoh nyata:
dibandingkan dengan:
pemberitahuan bagaimana dalam contoh pertama, karena
b
dana
referensi objek yang sama, ketika saya menggunakan+=
padab
, itu benar-benar berubahb
(dana
melihat perubahan itu juga - Setelah semua, itu referensi daftar yang sama). Namun dalam kasus kedua, ketika saya melakukannyab = b + [1, 2, 3]
, ini mengambil daftar yangb
mereferensikan dan menyatukannya dengan daftar baru[1, 2, 3]
. Itu kemudian menyimpan daftar gabungan di namespace saat ini sebagaib
- Tanpa memperhatikan apab
baris sebelumnya.1 Dalam ekspresi
x + y
, jikax.__add__
tidak diimplementasikan atau jikax.__add__(y)
kembaliNotImplemented
danx
dany
memiliki jenis yang berbeda , makax + y
cobalah menelepony.__radd__(x)
. Jadi, dalam kasus di mana Anda milikifoo_instance += bar_instance
jika
Foo
tidak menerapkan__add__
atau__iadd__
hasilnya di sini sama denganfoo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2 Dalam ekspresi
foo_instance + bar_instance
,bar_instance.__radd__
akan dicoba sebelumnyafoo_instance.__add__
jika jenisnyabar_instance
adalah subkelas dari jenisfoo_instance
(misalnyaissubclass(Bar, Foo)
). Rasional untuk ini adalah karenaBar
dalam beberapa arti "-tingkat yang lebih tinggi" objek dariFoo
sehinggaBar
harus mendapatkan pilihan utamaFoo
perilaku 's.sumber
+=
panggilan__iadd__
jika ada , dan kembali ke menambah dan membatalkan sebaliknya. Itu sebabnyai = 1; i += 1
bekerja meskipun tidak adaint.__iadd__
. Tapi selain nit kecil itu, penjelasannya bagus sekali.int.__iadd__
baru saja menelepon__add__
. Saya senang telah mempelajari sesuatu yang baru hari ini :).x + y
panggilany.__radd__(x)
jikax.__add__
tidak ada (atau kembaliNotImplemented
danx
dany
dari jenis yang berbeda)nb_inplace_add
atausq_inplace_concat
, dan fungsi-fungsi C API memiliki persyaratan yang lebih ketat daripada metode Python dunder, dan ... Tapi saya tidak berpikir itu relevan dengan jawabannya. Perbedaan utamanya adalah+=
mencoba melakukan penambahan di tempat sebelum kembali bertindak seperti+
, yang menurut saya sudah Anda jelaskan.Di balik selimut,
i += 1
lakukan sesuatu seperti ini:Sementara
i = i + 1
melakukan sesuatu seperti ini:Ini adalah penyederhanaan yang sedikit berlebihan, tetapi Anda mendapatkan ide: Python memberikan tipe cara untuk menangani
+=
secara khusus, dengan menciptakan__iadd__
metode dan juga__add__
.Tujuannya adalah bahwa tipe yang dapat berubah, seperti
list
, akan bermutasi di__iadd__
(dan kemudian kembaliself
, kecuali jika Anda melakukan sesuatu yang sangat rumit), sedangkan tipe yang tidak dapat diubah, sepertiint
, tidak akan mengimplementasikannya.Sebagai contoh:
Karena
l2
objeknya sama denganl1
, dan Anda bermutasil1
, Anda juga bermutasil2
.Tapi:
Di sini, Anda tidak bermutasi
l1
; alih-alih, Anda membuat daftar barul1 + [3]
,, dan memunculkan kembali namal1
untuk menunjuk padanya, meninggalkanl2
menunjuk pada daftar asli.(Dalam
+=
versi itu, kamu juga memberontakl1
, hanya saja dalam kasus itu kamulist
mengikatnya dengan yang sudah terikat, jadi kamu biasanya bisa mengabaikan bagian itu.)sumber
__iadd__
benar - benar memanggil__add__
jika adaAttributeError
?i.__iadd__
jangan menelepon__add__
; itui += 1
yang memanggil__add__
.i = i.__iadd__(1)
-iadd
dapat memodifikasi objek di tempat, tetapi tidak harus, dan diharapkan mengembalikan hasilnya dalam kedua kasus.operator.iadd
panggilan__add__
aktifAttributeError
, tetapi tidak dapat mengulang hasilnya ... jadii=1; operator.iadd(i, 1)
kembalikan 2 dani
setel ke1
. Yang agak membingungkan.Berikut adalah contoh yang secara langsung dibandingkan
i += x
dengani = i + x
:sumber