Apa perbedaan antara atribut class dan instance?

132

Apakah ada perbedaan yang berarti antara:

class A(object):
    foo = 5   # some default value

vs.

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

Jika Anda membuat banyak contoh, apakah ada perbedaan dalam kinerja atau persyaratan ruang untuk dua gaya? Ketika Anda membaca kode, apakah Anda menganggap arti dari kedua gaya tersebut sangat berbeda?

Dan Homerick
sumber
1
Saya baru menyadari bahwa pertanyaan serupa ditanyakan dan dijawab di sini: stackoverflow.com/questions/206734/... Haruskah saya menghapus pertanyaan ini?
Dan Homerick
2
Ini pertanyaan Anda, jangan ragu untuk menghapusnya. Karena itu milikmu, mengapa bertanya pendapat orang lain?
S.Lott

Jawaban:

146

Di luar pertimbangan kinerja, ada perbedaan semantik yang signifikan . Dalam kasus atribut kelas, hanya ada satu objek yang dirujuk. Dalam instance-atribut-set-at-instantiation, mungkin ada beberapa objek yang dirujuk. Misalnya

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]
Alex Coventry
sumber
4
Hanya jenis yang dapat diubah yang dibagikan. Suka intdan strmereka masih terikat dengan masing-masing instance daripada kelas.
Babu
11
@Babu: Tidak, intdan stryang juga bersama, dengan cara yang persis sama. Anda dapat memeriksa dengan mudah dengan isatau id. Atau lihat saja setiap instance __dict__dan kelas __dict__. Biasanya tidak terlalu menjadi masalah apakah tipe yang tidak dapat dibagi dibagikan atau tidak.
abarnert
17
Namun, perhatikan bahwa jika Anda melakukannya a.foo = 5, maka dalam kedua kasus Anda akan melihat b.fookembali []. Itu karena dalam kasus pertama, Anda menimpa atribut kelas a.foodengan atribut instance baru dengan nama yang sama.
Konstantin Schubert
39

Perbedaannya adalah bahwa atribut pada kelas dibagi oleh semua instance. Atribut pada instance adalah unik untuk instance itu.

Jika berasal dari C ++, atribut pada kelas lebih seperti variabel anggota statis.

Peter Shinners
sumber
1
Bukankah ini hanya tipe yang bisa diubah yang dibagikan? Jawaban yang diterima menunjukkan daftar, yang berfungsi, tetapi jika itu int, tampaknya sama dengan instance attr: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe
6
@Rafe: Tidak, semua tipe dibagikan. Alasan Anda bingung adalah apa a.foo.append(5)yang mengubah nilai yang a.foomerujuk, sementara a.foo = 5membuat a.foonama baru untuk nilai tersebut 5. Jadi, Anda berakhir dengan atribut instance yang menyembunyikan atribut class. Coba hal yang sama a.foo = 5dalam versi Alex dan Anda akan melihat bahwa b.fooitu tidak berubah.
abarnert
30

Ini adalah posting yang sangat bagus , dan ringkasannya seperti di bawah ini.

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

Dan dalam bentuk visual

masukkan deskripsi gambar di sini

Penugasan atribut kelas

  • Jika atribut kelas diatur dengan mengakses kelas, itu akan menimpa nilai untuk semua instance

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
  • Jika variabel kelas diatur dengan mengakses sebuah instance, itu akan menimpa nilai hanya untuk instance itu . Ini pada dasarnya menimpa variabel kelas dan mengubahnya menjadi variabel instan yang tersedia, secara intuitif, hanya untuk instance tersebut .

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1

Kapan Anda akan menggunakan atribut class?

  • Menyimpan konstanta . Karena atribut kelas dapat diakses sebagai atribut dari kelas itu sendiri, sering kali baik menggunakannya untuk menyimpan konstanta khusus kelas-lebar

    class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
              self.radius = radius   
        def area(self):
             return Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159
  • Menentukan nilai default . Sebagai contoh sepele, kita dapat membuat daftar terbatas (yaitu, daftar yang hanya dapat menampung sejumlah elemen atau lebih sedikit) dan memilih untuk memiliki batas default 10 item

    class MyClass(object):
        limit = 10
    
        def __init__(self):
            self.data = []
        def item(self, i):
            return self.data[i]
    
        def add(self, e):
            if len(self.data) >= self.limit:
                raise Exception("Too many elements")
            self.data.append(e)
    
     MyClass.limit
     ## 10
zangw
sumber
19

Karena orang-orang di komentar di sini dan di dua pertanyaan lain yang ditandai sebagai dups semuanya tampak bingung tentang hal ini dengan cara yang sama, saya pikir ada baiknya menambahkan jawaban tambahan di atas jawaban Alex Coventry .

Fakta bahwa Alex memberikan nilai dari tipe yang bisa berubah, seperti daftar, tidak ada hubungannya dengan apakah semuanya dibagikan atau tidak. Kita dapat melihat ini dengan idfungsi atau isoperator:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(Jika Anda bertanya-tanya mengapa saya menggunakan object()alih-alih, katakanlah, 5itu untuk menghindari dua masalah lain yang sama sekali tidak ingin saya bahas di sini; karena dua alasan berbeda, sepenuhnya diciptakan secara terpisah 5dapat berakhir menjadi contoh nomor yang sama 5. Tapi seluruhnya dibuat secara terpisah object()tidak bisa.)


Jadi, mengapa hal itu a.foo.append(5)dalam contoh Alex memengaruhi b.foo, tetapi a.foo = 5dalam contoh saya tidak? Nah, coba a.foo = 5dalam contoh Alex, dan perhatikan bahwa itu tidak mempengaruhi di b.foosana juga .

a.foo = 5hanya membuat a.foomenjadi nama untuk 5. Itu tidak mempengaruhi b.foo, atau nama lain untuk nilai lama yang a.foodigunakan untuk merujuk. * Agak sulit bahwa kita membuat atribut instance yang menyembunyikan atribut kelas, ** tapi begitu Anda mendapatkannya, tidak ada yang rumit adalah terjadi di sini.


Mudah-mudahan sekarang jelas mengapa Alex menggunakan daftar: fakta bahwa Anda dapat bermutasi daftar berarti lebih mudah untuk menunjukkan bahwa dua variabel nama daftar yang sama, dan juga berarti lebih penting dalam kode kehidupan nyata untuk mengetahui apakah Anda memiliki dua daftar atau dua nama untuk daftar yang sama.


* Kebingungan bagi orang-orang yang berasal dari bahasa seperti C ++ adalah bahwa dalam Python, nilai tidak disimpan dalam variabel. Nilai hidup di tanah nilai, sendiri, variabel hanya nama untuk nilai, dan tugas hanya membuat nama baru untuk nilai. Jika ini membantu, pikirkan setiap variabel Python sebagai shared_ptr<T>bukan T.

** Beberapa orang memanfaatkan ini dengan menggunakan atribut kelas sebagai "nilai default" untuk atribut instance yang instans mungkin atau mungkin tidak disetel. Ini bisa berguna dalam beberapa kasus, tetapi juga bisa membingungkan, jadi berhati-hatilah.

abarnert
sumber
0

Ada satu situasi lagi.

Atribut class dan instance adalah Descriptor .

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Di atas akan menampilkan:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Jenis akses instance yang sama melalui kelas atau instance mengembalikan hasil yang berbeda!

Dan saya menemukan definisi c.PyObject_GenericGetAttr, dan sebuah postingan yang bagus .

Menjelaskan

Jika atribut ditemukan di kamus kelas yang membentuk. yang objek MRO, kemudian memeriksa untuk melihat apakah atribut makhluk mendongak poin untuk Data Descriptor (yang tidak lebih bahwa kelas mengimplementasikan kedua __get__dan __set__metode). Jika ya, selesaikan pencarian atribut dengan memanggil __get__metode Deskriptor Data (baris 28–33).

SimonShyu
sumber