Inilah pertanyaan murni desain khusus Python:
class MyClass(object):
...
def get_my_attr(self):
...
def set_my_attr(self, value):
...
dan
class MyClass(object):
...
@property
def my_attr(self):
...
@my_attr.setter
def my_attr(self, value):
...
Python memungkinkan kita melakukannya dengan cara apa pun. Jika Anda akan merancang program Python, pendekatan mana yang akan Anda gunakan dan mengapa?
sumber
Dalam Python Anda tidak menggunakan getter atau setter atau properti hanya untuk bersenang-senang. Pertama-tama Anda hanya menggunakan atribut dan kemudian, hanya jika diperlukan, akhirnya bermigrasi ke properti tanpa harus mengubah kode menggunakan kelas Anda.
Memang ada banyak kode dengan ekstensi .py yang menggunakan getter dan setter dan kelas inheritance dan pointless di mana-mana di mana misalnya tuple sederhana akan dilakukan, tapi itu kode dari orang yang menulis dalam C ++ atau Java menggunakan Python.
Itu bukan kode Python.
sumber
Menggunakan properti memungkinkan Anda mulai dengan akses atribut normal dan kemudian mencadangkannya dengan getter dan setter sesudahnya sebagaimana diperlukan .
sumber
Jawaban singkatnya adalah: properti menang dengan mudah . Selalu.
Kadang-kadang ada kebutuhan untuk getter dan setter, tetapi bahkan kemudian, saya akan "menyembunyikan" mereka ke dunia luar. Ada banyak cara untuk melakukan hal ini dengan Python (
getattr
,setattr
,__getattribute__
, dll ..., tapi yang sangat ringkas dan bersih adalah:Berikut ini adalah artikel singkat yang memperkenalkan topik getter dan setter dengan Python.
sumber
property
. ( Kelihatannya seperti tugas sederhana, tetapi disebut fungsi.) Oleh karena itu, "Pythonic" pada dasarnya adalah istilah yang tidak berarti, kecuali oleh definisi tautologis: "Konvensi Pythonic adalah hal-hal yang kita definisikan sebagai Pythonic."property
fitur mengancam untuk membuat ide aksioma Pythonic hampir tidak berharga. Jadi yang tersisa hanyalah daftar periksa.self
objek , setter eksplisit dapat membantu. Misalnya,user.email = "..."
tidak terlihat seperti itu dapat menimbulkan pengecualian karena sepertinya hanya mengatur atribut, sedangkanuser.set_email("...")
memperjelas bahwa mungkin ada efek samping seperti pengecualian.[ TL; DR? Anda dapat melewati bagian akhir untuk contoh kode .]
Saya sebenarnya lebih suka menggunakan idiom yang berbeda, yang sedikit terlibat untuk digunakan sebagai salah satu, tetapi bagus jika Anda memiliki kasus penggunaan yang lebih kompleks.
Sedikit latar belakang terlebih dahulu.
Properti berguna karena memungkinkan kita menangani pengaturan dan mendapatkan nilai secara terprogram tetapi tetap memungkinkan atribut diakses sebagai atribut. Kita dapat mengubah 'menjadikan' menjadi 'perhitungan' (pada dasarnya) dan kita dapat mengubah 'set' menjadi 'acara'. Jadi katakanlah kita memiliki kelas berikut, yang saya kodekan dengan getter dan setter seperti Java.
Anda mungkin bertanya-tanya mengapa saya tidak menelepon
defaultX
dandefaultY
menggunakan__init__
metode objek. Alasannya adalah untuk kasus kami, saya ingin mengasumsikan bahwasomeDefaultComputation
metode mengembalikan nilai yang bervariasi dari waktu ke waktu, katakan cap waktu, dan setiap kalix
(atauy
) tidak disetel (di mana, untuk tujuan contoh ini, "tidak disetel" berarti "set ke Tidak Ada ") Saya ingin nilai perhitungan defaultx
(atauy
).Jadi ini lumpuh karena sejumlah alasan yang dijelaskan di atas. Saya akan menulis ulang menggunakan properti:
Apa yang telah kita peroleh? Kami memperoleh kemampuan untuk menyebut atribut ini sebagai atribut meskipun, di balik layar, kami akhirnya menjalankan metode.
Tentu saja kekuatan sebenarnya dari properti adalah kita umumnya ingin metode ini melakukan sesuatu selain hanya mendapatkan dan menetapkan nilai (jika tidak ada gunanya menggunakan properti). Saya melakukan ini dalam contoh rajin saya. Kami pada dasarnya menjalankan badan fungsi untuk mengambil default kapan pun nilainya tidak ditetapkan. Ini adalah pola yang sangat umum.
Tapi apa yang kita kehilangan, dan apa yang tidak bisa kita lakukan?
Gangguan utama, dalam pandangan saya, adalah bahwa jika Anda mendefinisikan pengambil (seperti yang kita lakukan di sini), Anda juga harus menentukan pembuat. [1] Itu suara ekstra yang mengacaukan kode.
Gangguan lain adalah bahwa kita masih harus menginisialisasi
x
dany
nilai - nilai di__init__
. (Yah, tentu saja kita bisa menambahkannya menggunakansetattr()
tapi itu kode tambahan.)Ketiga, tidak seperti pada contoh mirip-Java, getter tidak dapat menerima parameter lain. Sekarang saya dapat mendengar Anda berkata, ya, jika itu mengambil parameter, itu bukan pengambil! Dalam pengertian resmi, itu benar. Tetapi dalam arti praktis tidak ada alasan kita tidak seharusnya dapat parameter parameter atribut bernama - seperti
x
- dan mengatur nilainya untuk beberapa parameter tertentu.Alangkah baiknya jika kita bisa melakukan sesuatu seperti:
sebagai contoh. Cara terdekat yang bisa kita dapatkan adalah mengganti tugas untuk menyiratkan beberapa semantik khusus:
dan tentu saja memastikan bahwa setter kami tahu cara mengekstraksi tiga nilai pertama sebagai kunci ke kamus dan mengatur nilainya ke angka atau sesuatu.
Tetapi bahkan jika kita melakukan itu, kita masih tidak dapat mendukungnya dengan properti karena tidak ada cara untuk mendapatkan nilai karena kita tidak bisa melewatkan parameter sama sekali kepada pengambil. Jadi kami harus mengembalikan semuanya, memperkenalkan asimetri.
Para pengambil / penyetel gaya Java tidak membiarkan kami menangani ini, tapi kami kembali membutuhkan pengambil / penentu.
Dalam benak saya apa yang benar-benar kita inginkan adalah sesuatu yang menangkap persyaratan berikut:
Pengguna mendefinisikan hanya satu metode untuk atribut yang diberikan dan dapat menunjukkan di sana apakah atribut tersebut hanya baca atau baca-tulis. Properti gagal tes ini jika atribut dapat ditulisi.
Tidak perlu bagi pengguna untuk mendefinisikan variabel tambahan yang mendasari fungsi, jadi kami tidak memerlukan
__init__
atausetattr
dalam kode. Variabel hanya ada oleh fakta kami telah membuat atribut gaya baru ini.Kode default apa pun untuk atribut dijalankan dalam tubuh metode itu sendiri.
Kita dapat mengatur atribut sebagai atribut dan merujuknya sebagai atribut.
Kita dapat parameterisasi atribut.
Dalam hal kode, kami ingin cara menulis:
dan kemudian dapat melakukan:
Dan seterusnya.
Kami juga ingin cara untuk melakukan ini untuk kasus khusus dari atribut parameterable, tetapi masih memungkinkan kasus penetapan default berfungsi. Anda akan melihat bagaimana saya menangani ini di bawah.
Sekarang ke intinya (yay! Intinya!). Solusi saya datang untuk ini adalah sebagai berikut.
Kami membuat objek baru untuk menggantikan gagasan tentang properti. Objek dimaksudkan untuk menyimpan nilai set variabel untuk itu, tetapi juga mempertahankan pegangan pada kode yang tahu cara menghitung default. Tugasnya adalah menyimpan set
value
atau menjalankannyamethod
jika nilai itu tidak disetel.Sebut saja sebuah
UberProperty
.Saya berasumsi di
method
sini adalah metode kelas,value
adalah nilai dariUberProperty
, dan saya telah menambahkanisSet
karenaNone
mungkin nilai nyata dan ini memungkinkan kita cara yang bersih untuk menyatakan memang ada "tidak ada nilai". Cara lain adalah semacam penjaga.Ini pada dasarnya memberi kita sebuah objek yang dapat melakukan apa yang kita inginkan, tetapi bagaimana kita sebenarnya meletakkannya di kelas kita? Nah, properti menggunakan dekorator; kenapa kita tidak bisa? Mari kita lihat bagaimana kelihatannya (mulai sekarang saya akan tetap menggunakan hanya satu 'atribut',
x
).Ini sebenarnya belum berfungsi, tentu saja. Kita harus menerapkan
uberProperty
dan memastikan itu menangani keduanya mendapat dan set.Mari kita mulai dengan get.
Upaya pertama saya adalah membuat objek UberProperty baru dan mengembalikannya:
Saya dengan cepat menemukan, tentu saja, bahwa ini tidak berfungsi: Python tidak pernah mengikat callable ke objek dan saya membutuhkan objek untuk memanggil fungsi. Bahkan membuat dekorator di kelas tidak bekerja, karena meskipun sekarang kita memiliki kelas, kita masih tidak memiliki objek untuk dikerjakan.
Jadi kita harus bisa berbuat lebih banyak di sini. Kita tahu bahwa suatu metode hanya perlu diwakili satu kali, jadi mari kita lanjutkan dan simpan dekorator kita, tetapi modifikasi
UberProperty
hanya menyimpanmethod
referensi:Itu juga tidak bisa dipanggil, jadi saat ini tidak ada yang berfungsi.
Bagaimana kita melengkapi gambar? Nah, apa yang kita dapatkan ketika kita membuat kelas contoh menggunakan dekorator baru kita:
dalam kedua kasus kami mendapatkan kembali
UberProperty
yang tentu saja tidak bisa dipanggil, jadi ini tidak banyak berguna.Yang kita butuhkan adalah beberapa cara untuk secara dinamis mengikat
UberProperty
instance yang dibuat oleh dekorator setelah kelas telah dibuat ke objek kelas sebelum objek tersebut dikembalikan ke pengguna untuk digunakan. Um, ya, itu__init__
panggilan telepon, kawan.Mari kita tuliskan apa yang kita inginkan hasil temuan kita menjadi yang pertama. Kami mengikat sebuah
UberProperty
contoh, jadi hal yang jelas untuk dikembalikan adalah BoundUberProperty. Di sinilah kita akan benar-benar mempertahankan statusx
atribut.Sekarang kita representasi; bagaimana cara mendapatkan ini ke suatu objek? Ada beberapa pendekatan, tetapi yang termudah untuk dijelaskan hanya menggunakan
__init__
metode untuk melakukan pemetaan itu. Pada saat__init__
ini disebut dekorator kami telah berjalan, jadi hanya perlu melihat melalui objek__dict__
dan memperbarui atribut mana pun di mana nilai atribut adalah tipeUberProperty
.Sekarang, properti-uber itu keren dan kita mungkin ingin menggunakannya banyak, jadi masuk akal untuk membuat kelas dasar yang melakukan ini untuk semua subclass. Saya pikir Anda tahu apa yang akan disebut kelas dasar.
Kami menambahkan ini, mengubah contoh kami untuk mewarisi
UberObject
, dan ...Setelah dimodifikasi
x
menjadi:Kami dapat menjalankan tes sederhana:
Dan kami mendapatkan hasil yang kami inginkan:
(Wah, saya bekerja lembur.)
Perhatikan bahwa saya telah menggunakan
getValue
,setValue
danclearValue
di sini. Ini karena saya belum menautkan cara untuk mengembalikannya secara otomatis.Tetapi saya pikir ini adalah tempat yang baik untuk berhenti untuk saat ini, karena saya mulai lelah. Anda juga dapat melihat bahwa fungsionalitas inti yang kami inginkan ada di tempat; sisanya adalah ganti jendela. Ganti kegunaan jendela penting, tapi itu bisa menunggu sampai saya memiliki perubahan untuk memperbarui posting.
Saya akan menyelesaikan contoh di posting berikutnya dengan membahas hal-hal ini:
Kita perlu memastikan bahwa UberObject
__init__
selalu dipanggil oleh subclass.Kita perlu memastikan bahwa kita menangani kasus umum di mana seseorang 'alias' berfungsi untuk sesuatu yang lain, seperti:
Kami harus
e.x
kembalie.x.getValue()
secara default.e.x.getValue()
. (Melakukan ini sudah jelas, jika Anda belum memperbaikinya.)Kita perlu mendukung pengaturan
e.x directly
, seperti padae.x = <newvalue>
. Kita juga bisa melakukan ini di kelas induk, tetapi kita harus memperbarui__init__
kode kita untuk menanganinya.Akhirnya, kami akan menambahkan atribut parameter. Seharusnya cukup jelas bagaimana kita akan melakukan ini juga.
Berikut kode yang ada hingga sekarang:
[1] Saya mungkin ketinggalan tentang apakah ini masih terjadi.
sumber
return self.x or self.defaultX()
ini adalah kode berbahaya. Apa yang terjadi kapanself.x == 0
?__getitem__
metode. Akan aneh, karena Anda akan memiliki python yang sama sekali tidak standar.Saya pikir keduanya memiliki tempat mereka. Salah satu masalah dengan menggunakan
@property
adalah sulit untuk memperluas perilaku getter atau setter di subclass menggunakan mekanisme kelas standar. Masalahnya adalah bahwa fungsi pengambil / penyetel sebenarnya tersembunyi di properti.Anda benar-benar bisa mendapatkan fungsi, misalnya dengan
Anda dapat mengakses fungsi pengambil dan penyetel seperti
C.p.fget
danC.p.fset
, tetapi Anda tidak dapat dengan mudah menggunakan fasilitas pewarisan metode normal (misalnya super) untuk memperluasnya. Setelah menggali seluk-beluk super, Anda memang bisa menggunakan super dengan cara ini:Namun, menggunakan super () cukup kikuk, karena properti harus didefinisikan ulang, dan Anda harus menggunakan mekanisme super (cls, cls) yang sedikit kontra-intuitif untuk mendapatkan salinan p.
sumber
Menggunakan properti bagi saya lebih intuitif dan lebih cocok dengan sebagian besar kode.
Perbandingan
vs.
bagi saya cukup jelas yang lebih mudah dibaca. Properti juga memungkinkan untuk variabel pribadi lebih mudah.
sumber
Saya lebih suka tidak menggunakan keduanya dalam banyak kasus. Masalah dengan properti adalah mereka membuat kelas kurang transparan. Terutama, ini adalah masalah jika Anda mengajukan pengecualian dari setter. Misalnya, jika Anda memiliki properti Account.email:
maka pengguna kelas tidak berharap bahwa menetapkan nilai ke properti dapat menyebabkan pengecualian:
Sebagai hasilnya, pengecualian mungkin tidak tertangani, dan baik menyebar terlalu tinggi dalam rantai panggilan untuk ditangani dengan benar, atau mengakibatkan traceback yang sangat tidak membantu yang disajikan kepada pengguna program (yang sayangnya terlalu umum di dunia python dan java ).
Saya juga akan menghindari penggunaan getter dan setter:
Alih-alih properti dan getter / setter saya lebih suka melakukan logika kompleks di tempat yang didefinisikan dengan baik seperti dalam metode validasi:
atau metode Account.save serupa.
Perhatikan bahwa saya tidak mencoba mengatakan bahwa tidak ada kasus ketika properti berguna, hanya saja Anda mungkin lebih baik jika Anda dapat membuat kelas Anda sederhana dan transparan sehingga Anda tidak membutuhkannya.
sumber
validate()
metode di kelas. Properti hanya digunakan ketika Anda memiliki logika kompleks di belakangobj.x = y
tugas sederhana , dan terserah apa logika itu.Saya merasa seperti properti adalah tentang membiarkan Anda mendapatkan overhead menulis getter dan setter hanya ketika Anda benar-benar membutuhkannya.
Budaya Java Programming sangat menyarankan untuk tidak pernah memberikan akses ke properti, dan sebaliknya, melalui getter dan setter, dan hanya yang benar-benar dibutuhkan. Agak verbose untuk selalu menulis potongan kode yang jelas ini, dan perhatikan bahwa 70% dari waktu mereka tidak pernah digantikan oleh logika non-sepele.
Dengan Python, orang-orang benar-benar peduli pada overhead semacam itu, sehingga Anda bisa merangkul latihan berikut:
@property
untuk mengimplementasikannya tanpa mengubah sintaks dari sisa kode Anda.sumber
Saya terkejut bahwa tidak ada yang menyebutkan bahwa properti merupakan metode terikat dari kelas deskriptor, Adam Donohue dan NeilenMarais mendapatkan ide ini tepat di pos mereka - bahwa getter dan setter adalah fungsi dan dapat digunakan untuk:
Ini menyajikan cara cerdas untuk menyembunyikan detail implementasi dan pengodean kode seperti ekspresi reguler, ketik gips, coba .. kecuali blok, pernyataan, atau nilai yang dihitung.
Secara umum melakukan CRUD pada suatu objek mungkin sering cukup biasa tetapi mempertimbangkan contoh data yang akan bertahan ke database relasional. ORM dapat menyembunyikan detail implementasi dari vernakular SQL tertentu dalam metode yang cenderung fget, fset, fdel didefinisikan dalam kelas properti yang akan mengelola mengerikan jika .. elif .. tangga lain yang sangat jelek dalam kode OO - mengekspos yang sederhana dan anggun
self.variable = something
dan singkirkan detail untuk pengembang menggunakan ORM.Jika seseorang berpikir tentang properti hanya sebagai sisa suram dari bahasa Bondage dan Disiplin (yaitu Jawa) mereka kehilangan titik deskriptor.
sumber
Dalam proyek kompleks saya lebih suka menggunakan properti read-only (atau getter) dengan fungsi setter eksplisit:
Dalam proyek yang panjang, debug dan refactoring membutuhkan lebih banyak waktu daripada menulis kode itu sendiri. Ada beberapa kelemahan untuk menggunakan
@property.setter
yang membuat debugging lebih sulit:1) python memungkinkan pembuatan atribut baru untuk objek yang ada. Ini membuat salah cetak berikut sangat sulit dilacak:
Jika objek Anda adalah algoritma yang rumit, maka Anda akan menghabiskan cukup banyak waktu untuk mencari tahu mengapa itu tidak bertemu (perhatikan 't' tambahan pada baris di atas)
2) setter kadang-kadang mungkin berevolusi menjadi metode yang rumit dan lambat (misalnya memukul basis data). Akan sangat sulit bagi pengembang lain untuk mengetahui mengapa fungsi berikut ini sangat lambat. Dia mungkin menghabiskan banyak waktu pada
do_something()
metode pembuatan profil , sementaramy_object.my_attr = 4.
sebenarnya penyebab perlambatan:sumber
Baik
@property
getter tradisional dan setter memiliki keuntungan. Itu tergantung pada kasus penggunaan Anda.Keuntungan dari
@property
Anda tidak perlu mengubah antarmuka saat mengubah implementasi akses data. Ketika proyek Anda kecil, Anda mungkin ingin menggunakan akses atribut langsung untuk mengakses anggota kelas. Sebagai contoh, katakanlah Anda memiliki objek
foo
tipeFoo
, yang memiliki anggotanum
. Maka Anda bisa mendapatkan anggota ini dengannum = foo.num
. Ketika proyek Anda tumbuh, Anda mungkin merasa perlu ada beberapa pemeriksaan atau debug pada akses atribut sederhana. Maka Anda dapat melakukannya dengan@property
di dalam kelas. Antarmuka akses data tetap sama sehingga tidak perlu mengubah kode klien.Dikutip dari PEP-8 :
Menggunakan
@property
untuk akses data di Python dianggap sebagai Pythonic :Ini dapat memperkuat identifikasi diri Anda sebagai programmer Python (bukan Java).
Ini dapat membantu wawancara pekerjaan Anda jika pewawancara Anda menganggap getter dan setter gaya Jawa adalah anti-pola .
Keuntungan dari getter dan setter tradisional
Getter dan setter tradisional memungkinkan akses data yang lebih rumit daripada akses atribut sederhana. Misalnya, ketika Anda menetapkan anggota kelas, kadang-kadang Anda memerlukan bendera yang menunjukkan di mana Anda ingin memaksakan operasi ini bahkan jika ada sesuatu yang tidak terlihat sempurna. Meskipun tidak jelas bagaimana cara menambah akses anggota langsung
foo.num = num
, Anda dapat dengan mudah menambah setter tradisional Anda denganforce
parameter tambahan :Getter dan setter tradisional menjelaskan bahwa akses anggota kelas adalah melalui metode. Ini berarti:
Apa yang Anda dapatkan sebagai hasilnya mungkin tidak sama dengan apa yang disimpan di dalam kelas itu.
Bahkan jika akses terlihat seperti akses atribut sederhana, kinerjanya dapat sangat bervariasi dari itu.
Kecuali jika pengguna kelas Anda mengharapkan
@property
persembunyian di balik setiap pernyataan akses atribut, membuat hal-hal seperti itu eksplisit dapat membantu meminimalkan kejutan pengguna kelas Anda.Seperti yang disebutkan oleh @NeilenMarais dan dalam posting ini , memperluas getter tradisional dan setter dalam subclass lebih mudah daripada memperluas properti.
Getter dan setter tradisional telah banyak digunakan sejak lama dalam berbagai bahasa. Jika Anda memiliki orang-orang dari latar belakang yang berbeda di tim Anda, mereka terlihat lebih akrab daripada
@property
. Juga, seiring pertumbuhan proyek Anda, jika Anda mungkin perlu bermigrasi dari Python ke bahasa lain yang tidak memiliki@property
, menggunakan getter dan setter tradisional akan membuat migrasi lebih lancar.Peringatan
Baik
@property
getter dan setter tradisional tidak membuat anggota kelas pribadi, bahkan jika Anda menggunakan garis bawah dua kali sebelum namanya:sumber
Berikut adalah kutipan dari "Python Efektif: 90 Cara Tertentu untuk Menulis Python Lebih Baik" (Buku yang luar biasa. Saya sangat merekomendasikannya).
sumber