The +=
operator dalam python tampaknya beroperasi tiba-tiba pada daftar. Adakah yang bisa memberi tahu saya apa yang terjadi di sini?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
KELUARAN
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
tampaknya memengaruhi setiap contoh kelas, sedangkan foo = foo + bar
tampaknya berperilaku dengan cara yang saya harapkan.
The +=
operator disebut "senyawa tugas operator".
python
augmented-assignment
eucalculia.dll
sumber
sumber
+
operator pada array. Saya pikir itu masuk akal dalam kasus ini yang+=
akan ditambahkan.Jawaban:
Jawaban umumnya adalah
+=
mencoba memanggil__iadd__
metode khusus, dan jika itu tidak tersedia, ia mencoba menggunakan__add__
sebagai gantinya. Jadi masalahnya ada pada perbedaan antara metode khusus ini.The
__iadd__
Metode khusus adalah untuk tambahan di tempat, yang itu bermutasi objek yang ia bertindak atas. The__add__
metode khusus mengembalikan objek baru dan juga digunakan untuk standar+
operator.Jadi, ketika
+=
operator digunakan pada objek yang telah__iadd__
ditentukan, objek dimodifikasi di tempat. Jika tidak, ia malah akan mencoba menggunakan dataran__add__
dan mengembalikan objek baru.Itulah mengapa untuk tipe yang bisa berubah seperti daftar
+=
mengubah nilai objek, sedangkan untuk tipe yang tidak bisa diubah seperti tupel, string dan integer, objek baru dikembalikan sebagai gantinya (a += b
menjadi setara dengana = a + b
).Untuk tipe yang mendukung keduanya
__iadd__
dan__add__
oleh karena itu Anda harus berhati-hati yang mana yang Anda gunakan.a += b
akan memanggil__iadd__
dan bermutasia
, sedangkana = a + b
akan membuat objek baru dan menetapkannyaa
. Mereka bukanlah operasi yang sama!Untuk tipe yang tidak dapat diubah (jika Anda tidak memiliki
__iadd__
)a += b
dana = a + b
yang setara. Inilah yang memungkinkan Anda menggunakan+=
tipe yang tidak dapat diubah, yang mungkin tampak seperti keputusan desain yang aneh sampai Anda mempertimbangkannya jika tidak, Anda tidak dapat menggunakannya+=
pada tipe yang tidak dapat diubah seperti angka!sumber
__radd__
metode yang terkadang dipanggil (ini relevan untuk ekspresi yang melibatkan sebagian besar subclass).+=
sebenarnya memperluas daftar, ini menjelaskan mengapax = []; x = x + {}
memberiTypeError
waktux = []; x += {}
baru saja kembali[]
.Untuk kasus umum, lihat jawaban Scott Griffith . Namun, ketika berurusan dengan daftar seperti Anda,
+=
operator adalah singkatan darisomeListObject.extend(iterableObject)
. Lihat dokumentasi extender () .The
extend
fungsi akan menambahkan semua elemen parameter ke dalam daftar.Saat melakukan
foo += something
Anda memodifikasi daftarfoo
di tempat, sehingga Anda tidak mengubah referensi yang ditunjuk oleh namafoo
, tetapi Anda mengubah objek daftar secara langsung. Denganfoo = foo + something
, Anda sebenarnya sedang membuat daftar baru .Kode contoh ini akan menjelaskannya:
Perhatikan bagaimana referensi berubah saat Anda menetapkan kembali daftar baru
l
.Sebagai
bar
variabel kelas dan bukan variabel instan, memodifikasi di tempat akan mempengaruhi semua instance kelas itu. Namun saat mendefinisikan ulangself.bar
, instance akan memiliki variabel instance terpisahself.bar
tanpa memengaruhi instance kelas lainnya.sumber
a += b
itu berbeda daria = a + b
untuk dua daftara
danb
. Tapi itu masuk akal;extend
lebih sering menjadi hal yang dimaksudkan untuk dilakukan dengan daftar daripada membuat salinan baru dari seluruh daftar yang akan memiliki kompleksitas waktu yang lebih tinggi. Jika pengembang perlu berhati-hati agar mereka tidak mengubah daftar asli pada tempatnya, maka tupel adalah pilihan yang lebih baik sebagai objek yang tidak dapat diubah.+=
dengan tupel tidak dapat memodifikasi tupel asli.Masalahnya di sini adalah,
bar
didefinisikan sebagai atribut kelas, bukan variabel instan.Di
foo
, atribut class diubah dalaminit
metode, itulah sebabnya semua instance terpengaruh.Dalam
foo2
, variabel instance didefinisikan menggunakan atribut kelas (kosong), dan setiap instance mendapatkan miliknya sendiribar
.Penerapan yang "benar" akan menjadi:
Tentu saja, atribut kelas sepenuhnya legal. Faktanya, Anda dapat mengakses dan memodifikasinya tanpa membuat instance kelas seperti ini:
sumber
Ada dua hal yang terlibat di sini:
+
operator memanggil__add__
metode tersebut pada daftar. Ini mengambil semua elemen dari operannya dan membuat daftar baru yang berisi elemen-elemen yang menjaga urutannya.+=
__iadd__
metode panggilan operator pada daftar. Dibutuhkan iterable dan menambahkan semua elemen dari iterable ke daftar di tempatnya. Itu tidak membuat objek daftar baru.Di kelas
foo
pernyataanself.bar += [x]
tersebut bukanlah pernyataan tugas tetapi sebenarnya diterjemahkan menjadiyang mengubah daftar di tempat dan bertindak seperti metode daftar
extend
.Sebaliknya
foo2
, di kelas , pernyataan tugas dalaminit
metodedapat didekonstruksi sebagai:
Instance ini tidak memiliki atribut
bar
(meskipun demikian, ada atribut kelas dengan nama yang sama) sehingga ia mengakses atribut kelasbar
dan membuat daftar baru dengan menambahkannyax
. Pernyataan itu diterjemahkan menjadi:Kemudian itu membuat atribut instance
bar
dan menetapkan daftar yang baru dibuat ke dalamnya. Perhatikan bahwabar
di rhs tugas berbeda daribar
di lhs.Untuk contoh kelas
foo
,bar
adalah atribut kelas dan bukan atribut contoh. Karenanya setiap perubahan pada atribut kelasbar
akan tercermin untuk semua contoh.Sebaliknya, setiap instance kelas
foo2
memiliki atribut instance sendiri-sendiribar
yang berbeda dengan atribut kelas yang bernama samabar
.Semoga ini membereskan semuanya.
sumber
Meskipun banyak waktu telah berlalu dan banyak hal yang benar telah dikatakan, tidak ada jawaban yang menggabungkan kedua efek tersebut.
Anda memiliki 2 efek:
+=
(seperti yang dinyatakan oleh Scott Griffiths )Di kelas
foo
,__init__
metode memodifikasi atribut kelas. Itu karenaself.bar += [x]
diterjemahkan menjadiself.bar = self.bar.__iadd__([x])
.__iadd__()
adalah untuk modifikasi di tempat, sehingga memodifikasi daftar dan mengembalikan referensi ke sana.Perhatikan bahwa instance dict diubah meskipun ini biasanya tidak diperlukan karena class dict sudah berisi tugas yang sama. Jadi detail ini hampir tidak diperhatikan - kecuali jika Anda melakukannya
foo.bar = []
setelahnya. Di sini kasusnyabar
tetap sama berkat fakta yang disebutkan.Di kelas
foo2
, bagaimanapun, kelasbar
digunakan, tetapi tidak disentuh. Sebagai gantinya, a[x]
ditambahkan padanya, membentuk objek baru, sepertiself.bar.__add__([x])
yang disebut di sini, yang tidak memodifikasi objek. Hasilnya dimasukkan ke dalam instance dict, lalu memberi instance daftar baru sebagai dict, sementara atribut kelas tetap dimodifikasi.Perbedaan antara
... = ... + ...
dan juga... += ...
mempengaruhi tugas setelahnya:Anda dapat memverifikasi identitas objek dengan
print id(foo), id(f), id(g)
(jangan lupa tambahan()
jika Anda menggunakan Python3).BTW:
+=
Operator disebut "penugasan tambahan" dan umumnya dimaksudkan untuk melakukan modifikasi di tempat sejauh mungkin.sumber
Jawaban lain sepertinya sudah cukup banyak, meskipun tampaknya layak dikutip dan mengacu pada Tugas Tertambah PEP 203 :
...
sumber
sumber
Kami melihat bahwa ketika kami mencoba untuk memodifikasi objek yang tidak dapat diubah (bilangan bulat dalam kasus ini), Python hanya memberi kami objek yang berbeda. Di sisi lain, kita dapat membuat perubahan pada objek yang bisa berubah (daftar) dan membuatnya tetap menjadi objek yang sama.
ref: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Lihat juga url di bawah untuk memahami shallowcopy dan deepcopy
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
sumber