Variabel instance vs. variabel kelas dengan Python

121

Saya memiliki kelas Python, yang saya hanya perlu satu contoh pada waktu proses, jadi itu akan cukup untuk memiliki atribut hanya sekali per kelas dan bukan per contoh. Jika akan ada lebih dari satu instance (yang tidak akan terjadi), semua instance harus memiliki konfigurasi yang sama. Saya ingin tahu opsi mana yang lebih baik atau lebih "idiomatis" Python.

Variabel kelas:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Variabel instance:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
deamon
sumber
4
Setelah membaca pertanyaan ini dan melihat jawabannya, salah satu pertanyaan pertama saya adalah, "Jadi, bagaimana cara mengakses variabel kelas?" --itu karena sampai saat ini saya hanya menggunakan variabel instan. Sebagai jawaban atas pertanyaan saya sendiri, Anda melakukannya melalui nama kelas itu sendiri, meskipun secara teknis Anda juga dapat melakukannya melalui sebuah instance. Berikut tautan untuk dibaca orang lain dengan pertanyaan yang sama: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Jawaban:

159

Jika Anda hanya memiliki satu instance, yang terbaik adalah membuat semua variabel per instance, hanya karena mereka akan diakses (sedikit) lebih cepat (satu level "pencarian" lebih sedikit karena "inheritance" dari kelas ke instance), dan tidak ada kerugian untuk menimbang keuntungan kecil ini.

Alex Martelli
sumber
7
Pernah mendengar tentang pola Borg? Hanya memiliki satu contoh adalah cara yang salah untuk memilikinya sejak awal.
Devin Jeanpierre
435
@Devin, ya, saya pernah mendengar tentang pola Borg, karena saya yang memperkenalkannya (pada tahun 2001, kode cfr.activestate.com/recipes/… ;-). Tapi tidak ada yang salah, dalam kasus sederhana, dengan hanya memiliki satu contoh tanpa penegakan hukum.
Alex Martelli
2
@ user1767754, mudah membuatnya sendiri python -mtimeit- tetapi baru saja melakukannya di python3.4 Saya perhatikan bahwa mengakses intvariabel kelas sebenarnya sekitar 5 hingga 11 nanodetik lebih cepat daripada variabel instan di workstation lama saya - tidak yakin apa codepath membuatnya begitu.
Alex Martelli
45

Lebih lanjut menggemakan saran Mike dan Alex dan menambahkan warna saya sendiri ...

Menggunakan atribut instance adalah tipikal ... Python yang lebih idiomatis. Atribut kelas tidak terlalu sering digunakan, karena kasus penggunaannya bersifat spesifik. Hal yang sama juga berlaku untuk metode statis dan kelas vs. metode "normal". Mereka adalah konstruksi khusus yang menangani kasus penggunaan tertentu, jika tidak, kode itu dibuat oleh programmer yang menyimpang yang ingin menunjukkan bahwa mereka tahu beberapa sudut yang tidak jelas dari pemrograman Python.

Alex menyebutkan dalam balasannya bahwa akses akan (sedikit) lebih cepat karena satu tingkat pencarian yang lebih sedikit ... izinkan saya menjelaskan lebih lanjut bagi mereka yang belum tahu tentang cara kerjanya. Ini sangat mirip dengan akses variabel - urutan pencariannya adalah:

  1. penduduk setempat
  2. nonlokal
  3. global
  4. bawaan

Untuk akses atribut, urutannya adalah:

  1. contoh
  2. kelas
  3. kelas dasar yang ditentukan oleh MRO (urutan resolusi metode)

Kedua teknik bekerja secara "inside-out", yang berarti sebagian besar objek lokal diperiksa terlebih dahulu, kemudian lapisan luar diperiksa secara berurutan.

Dalam contoh Anda di atas, misalkan Anda mencari pathatribut. Ketika menemukan referensi seperti " self.path", Python akan melihat atribut instance terlebih dahulu untuk kecocokan. Ketika gagal, itu memeriksa kelas dari mana objek itu dibuat. Akhirnya, itu akan mencari kelas dasar. Seperti yang dikatakan Alex, jika atribut Anda ditemukan dalam instans, atribut tersebut tidak perlu mencari di tempat lain, sehingga menghemat sedikit waktu Anda.

Namun, jika Anda bersikeras menggunakan atribut kelas, Anda memerlukan pencarian ekstra tersebut. Atau , alternatif Anda yang lain adalah merujuk ke objek melalui kelas alih-alih instance, misalnya, MyController.pathalih-alihself.path . Itu adalah pencarian langsung yang akan mengatasi pencarian yang ditangguhkan, tetapi seperti yang disebutkan alex di bawah ini, ini adalah variabel global, jadi Anda kehilangan sedikit yang Anda pikir akan disimpan (kecuali jika Anda membuat referensi lokal ke nama kelas [global] ).

Intinya adalah Anda harus menggunakan atribut instance hampir sepanjang waktu. Namun, ada kalanya atribut kelas adalah alat yang tepat untuk pekerjaan itu. Kode yang menggunakan keduanya pada saat yang sama akan membutuhkan ketekunan yang paling tinggi, karena menggunakan selfhanya akan memberi Anda objek atribut instance dan akses bayangan ke atribut kelas dengan nama yang sama. Dalam kasus ini, Anda harus menggunakan akses atribut dengan nama kelas untuk mereferensikannya.

wescpy
sumber
@wescpy, tapi MyControlleryang mendongak di GLOBALS, sehingga total biaya lebih tinggi dari self.pathmana pathadalah variabel instansi (karena selfmerupakan daerah dengan metode == super cepat lookup).
Alex Martelli
ah benar. tangkapan yang bagus. Saya kira satu-satunya solusi adalah membuat referensi lokal ... pada titik ini, itu tidak terlalu berharga.
wescpy
24

Jika ragu, Anda mungkin menginginkan atribut instance.

Atribut kelas paling baik disediakan untuk kasus-kasus khusus yang masuk akal. Satu-satunya kasus penggunaan yang sangat umum adalah metode. Tidak jarang menggunakan atribut kelas untuk konstanta hanya-baca yang perlu diketahui instance (meskipun satu-satunya manfaat ini adalah jika Anda juga menginginkan akses dari luar kelas), tetapi Anda harus berhati-hati dalam menyimpan status apa pun di dalamnya , yang jarang Anda inginkan. Bahkan jika Anda hanya akan memiliki satu contoh, Anda harus menulis kelas seperti yang Anda lakukan pada yang lain, yang biasanya berarti menggunakan atribut contoh.

Mike Graham
sumber
1
Variabel kelas adalah semacam konstanta hanya-baca. Jika Python membiarkan saya mendefinisikan konstanta, saya akan menulisnya sebagai konstanta.
deamon
1
@ deamon, saya sedikit lebih cenderung menempatkan konstanta saya sepenuhnya di luar definisi kelas dan menamainya dengan huruf kapital semua. Menempatkan mereka di dalam kelas juga tidak masalah. Membuatnya sebagai atribut instance tidak akan merugikan apa pun, tetapi mungkin agak aneh. Saya tidak berpikir ini adalah masalah di mana komunitas terlalu banyak berada di belakang salah satu opsi.
Mike Graham
@MikeGraham FWIW, Panduan Gaya Python Google menyarankan untuk menghindari variabel global yang mendukung variabel kelas. Namun ada pengecualian.
Dennis
Ini adalah tautan baru ke Panduan Gaya Python Google . Sekarang hanya ada tertulis: avoid global variablesdan definisinya adalah, bahwa variabel global juga variabel yang dideklarasikan sebagai atribut kelas. Namun, panduan gaya Python sendiri ( PEP-8 ) harus menjadi tempat pertama untuk menjawab pertanyaan semacam ini. Maka pikiran Anda sendiri harus menjadi alat pilihan (tentu saja Anda juga bisa mendapatkan ide dari Google, misalnya).
colidyre
4

Pertanyaan yang sama di Performa mengakses variabel kelas dengan Python - kode di sini diadaptasi dari @Edward Loper

Variabel Lokal adalah yang tercepat untuk diakses, cukup terikat dengan Variabel Modul, diikuti oleh Variabel Kelas, diikuti oleh Variabel Instans.

Ada 4 cakupan variabel yang dapat Anda akses:

  1. Variabel Instance (self.varname)
  2. Variabel Kelas (Classname.varname)
  3. Variabel Modul (VARNAME)
  4. Variabel Lokal (varname)

Ujian:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Hasil:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
sumber