Bagaimana cara menghindari berbagi data kelas di antara instance?

146

Yang saya inginkan adalah perilaku ini:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

Tentu saja, apa yang sebenarnya terjadi ketika saya mencetak adalah:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

Jelas mereka membagikan data di kelas a. Bagaimana saya mendapatkan contoh terpisah untuk mencapai perilaku yang saya inginkan?

8steve8
sumber
14
Tolong, jangan gunakan listsebagai nama atribut. listadalah fungsi buil-in untuk membangun daftar baru. Anda harus menulis kelas nama dengan huruf besar.
Marc Tuduri

Jawaban:

147

Kamu mau ini:

class a:
    def __init__(self):
        self.list = []

Mendeklarasikan variabel di dalam deklarasi kelas menjadikannya "anggota" kelas dan bukan anggota instan. Mendeklarasikan mereka di dalam __init__metode memastikan bahwa instance baru dari anggota dibuat di samping setiap instance baru objek, yang merupakan perilaku yang Anda cari.

abyx
sumber
5
Klarifikasi tambahan: jika Anda menetapkan ulang properti daftar di salah satu instance, itu tidak akan mempengaruhi yang lain. Jadi jika Anda melakukan sesuatu seperti x.list = [], Anda bisa mengubahnya dan tidak memengaruhi orang lain. Masalah yang Anda hadapi adalah bahwa x.listdan y.listdaftar yang sama, jadi ketika Anda memanggil menambahkan satu, itu mempengaruhi yang lain.
Matt Moriarity
Tetapi mengapa ini hanya terjadi pada daftar? Ketika saya mendeklarasikan integer atau string di luar init , itu tidak dibagi di antara objek? Adakah yang bisa membagikan tautan dokumen apa pun ke konsep ini?
Amal Ts
1
@AmalTs Sepertinya Anda tidak mengerti bagaimana tugas dalam python bekerja. Lihat video ini atau posting SO ini . Perilaku yang Anda lihat disebabkan oleh kenyataan bahwa Anda bermutasi daftar tetapi membatalkan referensi untuk int dan string.
Андрей Беньковский
4
@AmalTs Catatan: dianggap praktik yang buruk untuk menggunakan atribut kelas sebagai nilai default "malas" untuk atribut instance. Bahkan jika atributnya adalah tipe yang tidak dapat diubah, lebih baik menempatkannya di dalam __init__.
Андрей Беньковский
21

Jawaban yang diterima bekerja tetapi sedikit penjelasan tidak sakit.

Atribut kelas tidak menjadi atribut instance saat instance dibuat. Mereka menjadi atribut instan ketika nilai diberikan kepada mereka.

Dalam kode asli tidak ada nilai yang diberikan ke listatribut setelah instantiation; jadi itu tetap atribut kelas. Mendefinisikan daftar di dalam __init__karya karena __init__dipanggil setelah instantiation. Atau, kode ini juga akan menghasilkan output yang diinginkan:

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

Namun, skenario yang membingungkan dalam pertanyaan tidak akan pernah terjadi pada objek yang tidak dapat diubah seperti angka dan string, karena nilainya tidak dapat diubah tanpa penugasan. Misalnya kode yang mirip dengan aslinya dengan tipe atribut string berfungsi tanpa masalah:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

Jadi untuk meringkas: atribut kelas menjadi atribut instance jika dan hanya jika nilai diberikan kepada mereka setelah instantiation, sedang dalam __init__metode atau tidak . Ini adalah hal yang baik karena dengan cara ini Anda dapat memiliki atribut statis jika Anda tidak pernah menetapkan nilai ke atribut setelah instantiation.

jurgenreza
sumber
11

Anda menyatakan "daftar" sebagai "properti tingkat kelas" dan bukan "properti tingkat instance". Untuk memiliki properti yang dicakup pada tingkat instance, Anda perlu menginisialisasi mereka melalui referensi dengan parameter "mandiri" dalam __init__metode (atau di tempat lain tergantung pada situasinya).

Anda tidak benar-benar harus menginisialisasi properti instance dalam __init__metode tetapi itu membuat lebih mudah dipahami.

jldupont
sumber
11

Meskipun jawaban yang diterima tepat, saya ingin menambahkan sedikit deskripsi.

Mari kita lakukan latihan kecil

pertama-tama tentukan kelas sebagai berikut:

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

Jadi apa yang kita miliki di sini?

  • Kami memiliki kelas yang sangat sederhana yang memiliki atribut tempyang berupa string
  • Sebuah __init__metode yang setself.x
  • Set metode perubahan self.temp

Cukup lurus ke depan sejauh ini ya? Sekarang mari kita mulai bermain-main dengan kelas ini. Mari kita inisialisasi kelas ini terlebih dahulu:

a = A('Tesseract')

Sekarang lakukan hal berikut:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

Yah, a.tempbekerja seperti yang diharapkan tetapi bagaimana sih itu A.tempbekerja? Yah itu berhasil karena temp adalah atribut kelas. Segala sesuatu di python adalah objek. Di sini A juga merupakan objek kelas type. Dengan demikian atribut temp adalah atribut yang dimiliki oleh Akelas dan jika Anda mengubah nilai temp melalui A(dan bukan melalui turunan dari a), nilai yang diubah akan tercermin dalam semua turunan Akelas. Ayo maju dan lakukan itu:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

Menarik bukan? Dan perhatikan itu id(a.temp)dan id(A.temp)masih sama .

Setiap objek Python secara otomatis diberikan __dict__atribut, yang berisi daftar atributnya. Mari selidiki apa isi kamus ini untuk objek contoh kami:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

Perhatikan bahwa tempatribut terdaftar di antara Aatribut kelas sementara xterdaftar untuk instance.

Jadi bagaimana bisa kita mendapatkan nilai yang ditentukan a.tempjika bahkan tidak terdaftar sebagai contoh a. Nah itulah keajaiban __getattribute__()metode. Dalam Python sintaksis putus-putus secara otomatis memanggil metode ini sehingga ketika kita menulis a.temp, Python mengeksekusi a.__getattribute__('temp'). Metode itu melakukan tindakan pencarian atribut, yaitu menemukan nilai atribut dengan melihat di tempat yang berbeda.

Implementasi standar __getattribute__()pencarian pertama kamus internal ( dikt ) dari suatu objek, kemudian jenis objek itu sendiri. Dalam hal ini a.__getattribute__('temp')dijalankan pertama a.__dict__['temp']dan kemudiana.__class__.__dict__['temp']

Oke sekarang mari kita gunakan changemetode kami :

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

Nah sekarang kita telah menggunakan self, print(a.temp)memberi kita nilai berbeda print(A.temp).

Sekarang jika kita membandingkan id(a.temp)dan id(A.temp), mereka akan berbeda.

Swapnil
sumber
3

Ya, Anda harus mendeklarasikan dalam "konstruktor" jika Anda ingin daftar tersebut menjadi properti objek dan bukan properti kelas.

osanchezmon
sumber
3

Jadi, hampir setiap respons di sini tampaknya melewatkan satu titik tertentu. Variabel kelas tidak pernah menjadi variabel instan seperti yang ditunjukkan oleh kode di bawah ini. Dengan menggunakan metaclass untuk mencegat penugasan variabel di tingkat kelas, kita dapat melihat bahwa ketika a.myattr dipindahkan, metode sihir penugasan bidang di kelas tidak dipanggil. Ini karena tugas membuat variabel instance baru . Perilaku ini sama sekali tidak ada hubungannya dengan variabel kelas seperti yang ditunjukkan oleh kelas kedua yang tidak memiliki variabel kelas dan masih memungkinkan penugasan lapangan.

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

DALAM SINGKAT variabel Kelas tidak ada hubungannya dengan variabel instan.

Lebih jelas. Mereka kebetulan berada dalam ruang lingkup untuk pencarian contoh. Variabel kelas sebenarnya adalah variabel instan pada objek kelas itu sendiri. Anda juga dapat memiliki variabel metaclass jika Anda mau juga karena metaclasses sendiri adalah objek juga. Semuanya adalah objek apakah itu digunakan untuk membuat objek lain atau tidak, jadi jangan terikat dalam semantik bahasa lain yang menggunakan kelas kata. Dalam python, sebuah kelas benar-benar hanya sebuah objek yang digunakan untuk menentukan cara membuat objek lain dan apa perilaku mereka nantinya. Metaclasses adalah kelas yang membuat kelas, hanya untuk menggambarkan hal ini lebih jauh.

TheQabalist
sumber
0

Untuk melindungi variabel Anda yang dibagikan oleh instance lain, Anda perlu membuat variabel instance baru setiap kali Anda membuat instance. Saat Anda mendeklarasikan variabel di dalam kelas, itu adalah variabel kelas dan dibagikan oleh semua instance. Jika Anda ingin menjadikannya sebagai contoh, perlu menggunakan metode init untuk menginisialisasi ulang variabel seperti merujuk pada instance

Dari Objek dan Kelas Python oleh Programiz.com :

__init__()fungsi. Fungsi khusus ini dipanggil setiap kali objek baru dari kelas itu dipakai.

Jenis fungsi ini juga disebut konstruktor dalam Pemrograman Berorientasi Objek (OOP). Kami biasanya menggunakannya untuk menginisialisasi semua variabel.

Sebagai contoh:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
Projesh Bhoumik
sumber