Apakah ini praktik yang baik untuk mendeklarasikan variabel instan sebagai Tidak Ada dalam kelas dengan Python?


68

Pertimbangkan kelas berikut:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Rekan kerja saya cenderung mendefinisikannya seperti ini:

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

Alasan utama untuk ini adalah bahwa editor pilihan mereka menunjukkan properti untuk pelengkapan otomatis.

Secara pribadi, saya tidak suka yang terakhir, karena tidak masuk akal bahwa kelas memiliki sifat-sifat tersebut None.

Yang mana yang akan menjadi praktik yang lebih baik dan untuk alasan apa?


63
Jangan biarkan IDE Anda menentukan kode apa yang Anda tulis?
Martijn Pieters

14
Ngomong-ngomong: menggunakan IDE python yang tepat (misalnya PyCharm), mengatur atribut di __init__sudah menyediakan autocompletion dll. Juga, menggunakan Nonemencegah IDE untuk menyimpulkan jenis atribut yang lebih baik, jadi lebih baik menggunakan default yang masuk akal sebagai gantinya (ketika mungkin).
Bakuriu

Jika hanya untuk pelengkapan otomatis, Anda dapat menggunakan petunjuk tipe, dan dokumen tambahan akan menjadi nilai tambah juga.
dashesy

3
"Jangan biarkan IDE Anda mendikte kode apa yang Anda tulis" adalah masalah yang diperdebatkan. Pada Python 3.6 ada anotasi in-line dan typingmodul, yang memungkinkan Anda untuk memberikan petunjuk kepada IDE dan linter, jika hal semacam itu menggelitik keinginan Anda ...
cz

1
Tugas-tugas ini di tingkat kelas tidak berpengaruh pada sisa kode. Mereka tidak berpengaruh self. Bahkan jika self.nameatau self.agetidak ditugaskan di __init__mereka tidak akan muncul dalam contoh self, mereka hanya muncul di kelas Person.
jolvi

Jawaban:


70

Saya menyebut praktik buruk yang terakhir di bawah aturan "ini tidak melakukan apa yang Anda pikirkan".

Posisi rekan kerja Anda dapat ditulis ulang sebagai: "Saya akan membuat sekelompok variabel quasi-global kelas-statis yang tidak pernah diakses, tetapi yang mengambil ruang di berbagai tabel namespace kelas ( __dict__), hanya untuk membuat IDE saya lakukan sesuatu."


4
Agak seperti docstrings ;-) Tentu saja, Python memiliki mode berguna untuk menghapusnya -OO,, untuk beberapa yang membutuhkannya.
Steve Jessop

15
Ruang yang diambil sejauh ini merupakan alasan paling tidak penting dari konvensi ini. Selusin byte per nama (per proses). Saya merasa seperti telah membuang-buang waktu saya hanya membaca bagian dari jawaban Anda yang berkaitan dengan itu, itu betapa tidak pentingnya biaya ruang.

8
@delnan Saya setuju tentang ukuran memori dari entri menjadi tidak berarti, saya lebih berpikir tentang ruang logis / mental yang diambil, membuat debugging introspektif dan memerlukan lebih banyak membaca dan menyortir, saya kira. I ~ LL
StarWeaver

3
Sebenarnya, jika Anda paranoid tentang ruang, Anda akan melihat bahwa ini menghemat ruang, dan membuang waktu. Anggota yang tidak diinisialisasi gagal menggunakan nilai kelas, dan oleh karena itu tidak ada banyak salinan nama variabel yang dipetakan ke Tidak ada (satu untuk setiap contoh). Jadi biayanya beberapa byte di kelas dan penghematan beberapa byte per instance. Tetapi setiap pencarian nama variabel yang gagal menghabiskan sedikit waktu.
Jon Jay Obermark

2
Jika Anda khawatir tentang 8 byte Anda tidak akan menggunakan Python.
cz

26

1. Buat kode Anda mudah dimengerti

Kode dibaca lebih sering daripada tertulis. Jadikan tugas pengelola kode Anda lebih mudah (mungkin juga Anda sendiri tahun depan).

Saya tidak tahu tentang aturan keras apa pun, tetapi saya lebih suka memiliki keadaan instance di masa depan yang dinyatakan dengan jelas. Menabrak dengan AttributeErrorsudah cukup buruk. Tidak melihat dengan jelas siklus hidup atribut instance lebih buruk. Jumlah senam mental yang diperlukan untuk mengembalikan kemungkinan urutan panggilan yang mengarah pada atribut yang ditugaskan dapat dengan mudah menjadi non-sepele, yang menyebabkan kesalahan.

Jadi saya biasanya tidak hanya mendefinisikan segala sesuatu di konstruktor, tetapi juga berusaha untuk menjaga jumlah atribut yang dapat diubah ke minimum.

2. Jangan mencampur anggota tingkat kelas dan tingkat kejadian

Apa pun yang Anda tetapkan tepat di dalam classdeklarasi adalah milik kelas dan dibagikan oleh semua instance kelas. Misalnya ketika Anda mendefinisikan fungsi di dalam kelas, itu menjadi metode yang sama untuk semua instance. Hal yang sama berlaku untuk anggota data. Ini sama sekali berbeda dengan atribut instance yang biasanya Anda definisikan __init__.

Anggota data tingkat kelas paling berguna sebagai konstanta:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
Ya, tapi mencampurkan anggota level-level dan level-instans adalah yang dilakukan def . Ini menciptakan fungsi yang kita anggap sebagai atribut objek, tetapi benar-benar anggota kelas. Hal yang sama berlaku untuk properti dan sejenisnya. Memberi ilusi bahwa bekerja adalah milik objek ketika dimediasi oleh kelas adalah cara yang dihormati waktu untuk tidak menjadi gila. Bagaimana itu bisa seburuk itu, jika itu OK untuk Python itu sendiri.
Jon Jay Obermark

Nah, urutan resolusi metode dalam Python (juga berlaku untuk anggota data) tidak terlalu sederhana. Apa yang tidak ditemukan pada level instance, akan dicari pada level kelas, kemudian di antara kelas dasar, dll. Anda memang dapat membayangi anggota level kelas (data atau metode) dengan menugaskan anggota level-instance dengan nama yang sama. Tetapi metode level instance terikat ke instance selfdan tidak perlu selfdilewatkan, sementara metode level kelas tidak terikat dan fungsi polos seperti yang terlihat pada defwaktu, dan menerima instance sebagai argumen pertama. Jadi ini adalah hal yang berbeda.
9000

Saya pikir AttributeErrorsinyal itu bagus daripada bug. Jika tidak, Anda akan menelan Tidak Ada dan mendapatkan hasil yang tidak berarti. Khusus penting dalam kasus yang dihadapi, di mana atribut didefinisikan dalam __init__, sehingga atribut yang hilang (tetapi ada di tingkat kelas) hanya dapat disebabkan oleh warisan kereta.
Davidmh

@ Davidvidmh: Kesalahan yang terdeteksi selalu lebih baik daripada kesalahan yang tidak terdeteksi, memang! Saya akan mengatakan bahwa jika Anda harus membuat atribut Nonedan nilai ini tidak masuk akal pada saat konstruksi contoh, Anda memiliki masalah dalam arsitektur Anda dan harus memikirkan kembali siklus hidup dari nilai atribut atau nilai awalnya. Perhatikan bahwa dengan mendefinisikan atribut Anda lebih awal, Anda dapat mendeteksi masalah seperti itu bahkan sebelum Anda menulis seluruh kelas, apalagi menjalankan kode.
9000

Menyenangkan! misil! ngomong-ngomong, aku cukup yakin tidak masalah untuk membuat level level vars dan mencampurkannya ... selama level kelas mengandung default, dll.
Erik Aronesty

18

Secara pribadi saya mendefinisikan anggota dalam metode __ init __ (). Saya tidak pernah berpikir untuk mendefinisikan mereka di bagian kelas. Tetapi apa yang selalu saya lakukan: Saya init semua anggota dalam metode __ init__, bahkan mereka yang tidak diperlukan dalam metode __ init__.

Contoh:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

Saya pikir penting untuk mendefinisikan semua anggota di satu tempat. Itu membuat kode lebih mudah dibaca. Apakah itu di dalam __ init __ () atau di luar, tidak begitu penting. Tetapi penting bagi sebuah tim untuk berkomitmen pada gaya pengkodean yang kurang lebih sama.

Oh, dan Anda mungkin memperhatikan bahwa saya pernah menambahkan awalan "_" ke variabel anggota.


13
Anda harus menetapkan semua nilai dalam konstruktor. Jika nilai diatur dalam kelas itu sendiri, itu akan dibagi di antara instance, yang baik untuk Tidak ada, tetapi tidak untuk sebagian besar nilai. Jadi jangan ubah apa pun tentang itu. ;)
Remco Haszing

4
Nilai default untuk parameter, dan kemampuan untuk mengambil argumen posisi dan kata kunci yang sewenang-wenang membuatnya jauh lebih menyakitkan daripada yang diharapkan. (Dan sebenarnya mungkin untuk mendelegasikan konstruksi ke metode lain atau bahkan fungsi yang berdiri sendiri, jadi jika Anda benar - benar membutuhkan pengiriman ganda, Anda dapat melakukannya juga).
Sean Vieira

6
@Benedict Python tidak memiliki kontrol akses. Garis bawah utama adalah konvensi yang diterima untuk detail implementasi. Lihat PEP 8 .
Doval

3
@Doval Ada alasan yang sangat bagus untuk awalan nama atribut dengan _: Untuk menunjukkan bahwa itu pribadi! (Mengapa begitu banyak orang di utas ini membingungkan atau Python setengah membingungkan dengan bahasa lain?)

1
Ah, jadi Anda adalah salah satu dari orang-orang interaksi-properti-atau-fungsi-untuk-semua-terpapar ... Maaf salah paham. Gaya itu selalu tampak berlebihan bagi saya, tetapi memiliki pengikut.
Jon Jay Obermark

11

Ini adalah praktik yang buruk. Anda tidak perlu nilai-nilai itu, mereka mengacaukan kode, dan mereka dapat menyebabkan kesalahan.

Mempertimbangkan:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

Saya akan kesulitan untuk menyebutnya 'menyebabkan kesalahan'. Tidak jelas apa yang Anda maksud dengan meminta 'x' di sini, tetapi Anda mungkin sangat menginginkan nilai awal yang paling mungkin.
Jon Jay Obermark

Saya harus menjelaskan bahwa itu dapat menyebabkan kesalahan jika digunakan secara tidak benar.
Daenyth

Risiko lebih tinggi masih menginisialisasi objek. Tidak ada yang benar-benar aman. Satu-satunya kesalahan yang akan ditimbulkannya adalah menelan pengecualian yang benar-benar lemah.
Jon Jay Obermark

1
Gagal menyebabkan kesalahan adalah masalah jika kesalahan akan mencegah masalah yang lebih serius.
Erik Aronesty

0

Saya akan pergi dengan "sedikit seperti dokumen, lalu" dan menyatakan ini tidak berbahaya, selama itu selaluNone , atau kisaran sempit dari nilai-nilai lain, semua tidak berubah.

Itu berbau atavisme, dan lampiran berlebihan untuk bahasa yang diketik secara statis. Dan tidak ada gunanya sebagai kode. Tetapi memiliki tujuan kecil yang tersisa, dalam dokumentasi.

Ini mendokumentasikan apa nama yang diharapkan, jadi jika saya menggabungkan kode dengan seseorang dan salah satu dari kami memiliki 'nama pengguna' dan nama_pengguna lainnya, ada petunjuk bagi manusia bahwa kami telah berpisah dan tidak menggunakan variabel yang sama.

Memaksa inisialisasi penuh sebagai kebijakan mencapai hal yang sama dengan cara yang lebih Pythonic, tetapi jika ada kode aktual dalam __init__, ini menyediakan tempat yang lebih jelas untuk mendokumentasikan variabel yang digunakan.

Jelas masalah BESAR di sini adalah bahwa ia menggoda orang untuk menginisialisasi dengan nilai selain None, yang bisa buruk:

class X:
    v = {}
x = X()
x.v[1] = 2

meninggalkan jejak global dan tidak membuat turunan untuk x.

Tapi itu lebih merupakan kekhasan dalam Python secara keseluruhan daripada dalam praktik ini, dan kita seharusnya sudah paranoid tentang hal itu.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.