Bagaimana cara mendeklarasikan nilai default untuk variabel instance dengan Python?

90

Haruskah saya memberikan nilai default kepada anggota kelas saya seperti ini:

class Foo:
    num = 1

atau seperti ini?

class Foo:
    def __init__(self):
        self.num = 1

Dalam pertanyaan ini saya menemukan bahwa dalam kedua kasus,

bar = Foo()
bar.num += 1

adalah operasi yang terdefinisi dengan baik.

Saya mengerti bahwa metode pertama akan memberi saya variabel kelas sedangkan yang kedua tidak. Namun, jika saya tidak memerlukan variabel kelas, tetapi hanya perlu menetapkan nilai default untuk variabel instan saya, apakah kedua metode sama baiknya? Atau salah satunya lebih 'pythonic' dari yang lain?

Satu hal yang saya perhatikan adalah bahwa dalam tutorial Django, mereka menggunakan metode kedua untuk mendeklarasikan Model. Secara pribadi saya pikir metode kedua lebih elegan, tetapi saya ingin tahu apa cara 'standar' itu.

int3
sumber

Jawaban:

132

Memperluas jawaban bp, saya ingin menunjukkan kepada Anda apa yang dia maksud dengan tipe yang tidak berubah.

Pertama, ini tidak masalah:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Namun, ini hanya berfungsi untuk jenis yang tidak dapat diubah (tidak dapat diubah). Jika nilai default bisa berubah (artinya bisa diganti), ini yang akan terjadi:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Perhatikan bahwa keduanya adan bmemiliki atribut bersama. Ini seringkali tidak diinginkan.

Ini adalah cara Pythonic untuk menentukan nilai default untuk variabel instan, ketika tipenya bisa berubah:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

Alasan potongan kode pertama saya berfungsi adalah karena, dengan tipe yang tidak dapat diubah, Python membuat contoh baru kapan pun Anda menginginkannya. Jika Anda perlu menambahkan 1 ke 1, Python membuatkan 2 baru untuk Anda, karena 1 yang lama tidak dapat diubah. Alasannya sebagian besar untuk hashing, saya yakin.

Xavier Ho
sumber
2
Saya baru-baru ini menemukan cara yang jauh lebih sederhana untuk menerapkan nilai default untuk atribut yang bisa berubah. Cukup tulis self.attr = attr or []di dalam __init__metode. Itu mencapai hasil yang sama (menurut saya) dan masih jelas serta dapat dibaca.
Bill
4
Maaf teman-teman. Saya melakukan penelitian lebih lanjut tentang saran dalam komentar saya di atas dan ternyata itu tidak persis sama dan tidak disarankan .
Tagihan
Mengapa kelas TestC memiliki tanda kurung kosong? Apakah itu warisan Python 2?
ChrisG
55

Kedua cuplikan tersebut melakukan hal yang berbeda, jadi ini bukan masalah selera tetapi masalah perilaku apa yang benar dalam konteks Anda. Dokumentasi Python menjelaskan perbedaannya, tetapi berikut beberapa contohnya:

Bukti A

class Foo:
  def __init__(self):
    self.num = 1

Ini mengikat numke instance Foo . Perubahan ke bidang ini tidak diterapkan ke contoh lain.

Jadi:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Bukti B

class Bar:
  num = 1

Ini mengikat kelasnum Bar . Perubahan disebarkan!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Jawaban sebenarnya

Jika saya tidak memerlukan variabel kelas, tetapi hanya perlu menetapkan nilai default untuk variabel instan saya, apakah kedua metode sama baiknya? Atau salah satunya lebih 'pythonic' dari yang lain?

Kode dalam pameran B salah untuk ini: mengapa Anda ingin mengikat atribut kelas (nilai default pada pembuatan instance) ke satu instance?

Kode di pameran A tidak apa-apa.

Jika Anda ingin memberikan default untuk variabel contoh di konstruktor Anda, saya akan melakukan ini:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...atau bahkan:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... atau bahkan: (lebih disukai, tetapi jika dan hanya jika Anda berurusan dengan tipe yang tidak dapat diubah!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

Dengan cara ini Anda dapat melakukan:

foo1 = Foo(4)
foo2 = Foo() #use default
badp
sumber
1
adalah metode init yang digunakan dalam contoh ini berbeda dari underscore___init__underscore untuk inisialisasi @badp
Eliethesaiyan
Seharusnya contoh pertama tidakdef __init__(self, num = None)
PyWalker2797
6

Menggunakan anggota kelas untuk memberikan nilai default bekerja dengan sangat baik asalkan Anda berhati-hati untuk melakukannya dengan nilai yang tidak dapat diubah. Jika Anda mencoba melakukannya dengan daftar atau dikt yang akan sangat mematikan. Ini juga berfungsi di mana atribut instance adalah referensi ke kelas asalkan nilai defaultnya adalah None.

Saya telah melihat teknik ini digunakan dengan sangat sukses dalam repoze yang merupakan kerangka kerja yang berjalan di atas Zope. Keuntungannya di sini tidak hanya ketika kelas Anda disimpan ke database hanya atribut non-default yang perlu disimpan, tetapi juga ketika Anda perlu menambahkan bidang baru ke dalam skema, semua objek yang ada melihat bidang baru dengan defaultnya nilai tanpa perlu benar-benar mengubah data yang disimpan.

Saya merasa ini juga berfungsi dengan baik dalam pengkodean yang lebih umum, tetapi ini adalah gaya. Gunakan apa pun yang membuat Anda paling bahagia.

Duncan
sumber
3

Menggunakan anggota kelas untuk nilai default dari variabel instan bukanlah ide yang baik, dan ini pertama kalinya saya melihat ide ini disebutkan sama sekali. Ini berfungsi dalam contoh Anda, tetapi mungkin gagal dalam banyak kasus. Misalnya, jika nilainya bisa berubah, mutasi pada instance yang tidak dimodifikasi akan mengubah default:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
Max Shawabkeh
sumber
kecuali hanya yang pertama kali []diklon di sekitar sana; x.ldan y.lkeduanya adalah nama jahat dan nilai langsung berbeda yang terhubung ke rujukan yang sama. (Istilah pengacara bahasa dibuat)
Phlip
1

Anda juga dapat mendeklarasikan variabel kelas sebagai Tidak Ada yang akan mencegah propagasi. Ini berguna ketika Anda membutuhkan kelas yang didefinisikan dengan baik dan ingin mencegah AttributeErrors. Sebagai contoh:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Juga jika Anda membutuhkan default:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Tentu tetap ikuti info di jawaban lain tentang menggunakan tipe mutable vs immutable sebagai default di __init__.

Realistis
sumber
0

Dengan dataclass , fitur yang ditambahkan di Python 3.7, sekarang ada cara lain (cukup nyaman) untuk mencapai pengaturan nilai default pada instance kelas. Dekorator dataclassakan secara otomatis menghasilkan beberapa metode di kelas Anda, seperti konstruktor. Seperti catatan dokumentasi yang ditautkan di atas, "[t] variabel anggota yang digunakan dalam metode yang dihasilkan ini ditentukan menggunakan anotasi jenis PEP 526 ".

Mempertimbangkan contoh OP, kita bisa menerapkannya seperti ini:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Saat membangun sebuah objek dari tipe kelas ini, kita bisa secara opsional menimpa nilainya.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Johannes Gehrs
sumber