Zen of Python menyatakan bahwa seharusnya hanya ada satu cara untuk melakukan sesuatu - namun sering kali saya mengalami masalah dalam memutuskan kapan harus menggunakan suatu fungsi versus kapan harus menggunakan suatu metode.
Mari kita ambil contoh sepele- objek ChessBoard. Katakanlah kita membutuhkan cara untuk mendapatkan semua langkah resmi Raja yang tersedia di papan. Apakah kita menulis ChessBoard.get_king_moves () atau get_king_moves (papan_ catur)?
Berikut beberapa pertanyaan terkait yang saya lihat:
- Mengapa python menggunakan 'metode ajaib'?
- Apakah ada alasan string Python tidak memiliki metode panjang string?
Jawaban yang saya dapatkan sebagian besar tidak meyakinkan:
Mengapa Python menggunakan metode untuk beberapa fungsionalitas (misalnya list.index ()) tetapi fungsi untuk yang lain (misalnya len (list))?
Alasan utamanya adalah sejarah. Fungsi digunakan untuk operasi yang generik untuk sekelompok tipe dan yang dimaksudkan untuk bekerja bahkan untuk objek yang tidak memiliki metode sama sekali (misalnya tupel). Juga mudah untuk memiliki fungsi yang dapat dengan mudah diterapkan ke kumpulan objek yang tidak berbentuk saat Anda menggunakan fitur fungsional Python (map (), apply () et al).
Faktanya, mengimplementasikan len (), max (), min () sebagai fungsi bawaan sebenarnya lebih sedikit kode daripada mengimplementasikannya sebagai metode untuk setiap jenis. Seseorang dapat berdebat tentang kasus individu tetapi itu adalah bagian dari Python, dan sudah terlambat untuk membuat perubahan mendasar seperti itu sekarang. Fungsinya harus tetap ada untuk menghindari kerusakan kode besar-besaran.
Meskipun menarik, penjelasan di atas tidak terlalu menjelaskan tentang strategi apa yang harus diadopsi.
Inilah salah satu alasannya - dengan metode khusus, pengembang akan bebas memilih nama metode yang berbeda, seperti getLength (), length (), getlength () atau apa pun. Python memberlakukan penamaan yang ketat sehingga fungsi umum len () bisa digunakan.
Sedikit lebih menarik. Pandangan saya adalah bahwa fungsinya dalam arti tertentu, versi antarmuka Pythonic.
Terakhir, dari Guido sendiri :
Berbicara tentang Kemampuan / Antarmuka membuat saya berpikir tentang beberapa nama metode khusus "nakal" kami. Dalam Referensi Bahasa, dikatakan, "Sebuah kelas dapat mengimplementasikan operasi tertentu yang dipanggil oleh sintaks khusus (seperti operasi aritmatika atau subskrip dan pemotongan) dengan mendefinisikan metode dengan nama khusus." Tetapi ada semua metode ini dengan nama khusus seperti
__len__
atau__unicode__
yang tampaknya disediakan untuk kepentingan fungsi bawaan, daripada untuk dukungan sintaks. Agaknya dalam Python berbasis antarmuka, metode ini akan berubah menjadi metode yang dinamai secara teratur di ABC, jadi itu__len__
akan menjadiclass container: ... def len(self): raise NotImplemented
Padahal, memikirkannya lagi, saya tidak mengerti mengapa semua operasi sintaksis tidak hanya memanggil metode yang dinamai normal yang sesuai pada ABC tertentu. "
<
", misalnya, mungkin akan memanggil "object.lessthan
" (atau mungkin "comparable.lessthan
"). Jadi manfaat lain adalah kemampuan untuk menyapih Python dari keanehan nama yang rusak ini, yang menurut saya merupakan peningkatan HCI .Hm. Saya tidak yakin saya setuju (bayangkan :-).
Ada dua bagian "dasar pemikiran Python" yang ingin saya jelaskan terlebih dahulu.
Pertama-tama, saya memilih len (x) daripada x.len () karena alasan HCI (
def __len__()
datang jauh kemudian). Sebenarnya ada dua alasan yang saling terkait, keduanya HCI:(a) Untuk beberapa operasi, notasi prefiks hanya terbaca lebih baik daripada postfix - operasi prefiks (dan infiks!) memiliki tradisi panjang dalam matematika yang menyukai notasi di mana visual membantu ahli matematika memikirkan suatu masalah. Bandingkan kemudahan kita menulis ulang rumus like
x*(a+b)
intox*a + x*b
dengan kecanggungan melakukan hal yang sama menggunakan notasi OO mentah.(b) Ketika saya membaca kode yang mengatakan
len(x)
saya tahu bahwa itu menanyakan panjang sesuatu. Ini memberi tahu saya dua hal: hasilnya adalah integer, dan argumennya adalah semacam wadah. Sebaliknya, ketika saya membacax.len()
, saya harus tahu bahwa itux
adalah semacam wadah yang mengimplementasikan antarmuka atau mewarisi dari kelas yang memiliki standarlen()
. Saksi kebingungan kita kadang-kadang memiliki ketika kelas yang tidak melaksanakan pemetaan memilikiget()
ataukeys()
metode, atau sesuatu yang tidak file memilikiwrite()
metode.Mengatakan hal yang sama dengan cara lain, saya melihat 'len' sebagai operasi bawaan . Aku benci kehilangan itu. Saya tidak bisa mengatakan dengan pasti apakah Anda bersungguh-sungguh atau tidak, tetapi 'def len (self): ...' jelas terdengar seperti Anda ingin menurunkannya ke metode biasa. Saya sangat -1 tentang itu.
Bagian kedua dari alasan Python yang saya janjikan untuk menjelaskan adalah alasan mengapa saya memilih metode khusus untuk melihat
__special__
dan bukan hanyaspecial
. Saya mengantisipasi banyak operasi yang kelas mungkin ingin timpa, beberapa standar (misalnya__add__
atau__getitem__
), beberapa tidak begitu standar (misalnya acar__reduce__
untuk waktu yang lama tidak memiliki dukungan dalam kode C sama sekali). Saya tidak ingin operasi khusus ini menggunakan nama metode biasa, karena kelas yang sudah ada sebelumnya, atau kelas yang ditulis oleh pengguna tanpa memori ensiklopedis untuk semua metode khusus, akan bertanggung jawab untuk secara tidak sengaja menentukan operasi yang tidak ingin mereka implementasikan , dengan kemungkinan konsekuensi yang menghancurkan. Ivan Krstić menjelaskan hal ini dengan lebih ringkas dalam pesannya, yang muncul setelah saya menulis semua ini.- --Guido van Rossum (halaman muka: http://www.python.org/~guido/ )
Pemahaman saya tentang hal ini adalah bahwa dalam kasus-kasus tertentu, notasi prefiks lebih masuk akal (yaitu, Duck.quack lebih masuk akal daripada quack (Duck) dari sudut pandang linguistik.) Dan sekali lagi, fungsinya memungkinkan untuk "antarmuka".
Dalam kasus seperti itu, tebakan saya adalah mengimplementasikan get_king_moves hanya berdasarkan poin pertama Guido. Tapi itu masih menyisakan banyak pertanyaan terbuka tentang katakanlah, mengimplementasikan stack dan kelas antrian dengan metode push dan pop yang serupa- haruskah itu fungsi atau metode? (di sini saya akan menebak fungsi, karena saya benar-benar ingin memberi sinyal antarmuka push-pop)
TLDR: Dapatkah seseorang menjelaskan apa strategi untuk memutuskan kapan harus menggunakan fungsi vs metode?
sumber
X.frob
atauX.__frob__
dan berdiri sendirifrob
.Jawaban:
Aturan umum saya adalah ini - apakah operasi dilakukan pada objek atau objek?
jika itu dilakukan oleh objek, itu harus menjadi operasi anggota. Jika itu bisa berlaku untuk hal lain juga, atau dilakukan oleh sesuatu yang lain ke objek maka itu harus menjadi fungsi (atau mungkin anggota dari sesuatu yang lain).
Saat memperkenalkan pemrograman, adalah tradisional (meskipun penerapannya salah) untuk mendeskripsikan objek dalam istilah objek dunia nyata seperti mobil. Anda menyebut bebek, jadi mari kita lakukan itu.
Dalam konteks analogi "objek adalah benda nyata", adalah "benar" untuk menambahkan metode kelas untuk apa pun yang dapat dilakukan objek. Jadi, katakanlah saya ingin membunuh bebek, apakah saya menambahkan .kill () ke bebek? Tidak ... sejauh yang saya tahu hewan tidak bunuh diri. Karena itu jika saya ingin membunuh bebek, saya harus melakukan ini:
Beranjak dari analogi ini, mengapa kita menggunakan metode dan kelas? Karena kami ingin berisi data dan mudah-mudahan menyusun kode kami sedemikian rupa sehingga dapat digunakan kembali dan diperluas di masa mendatang. Ini membawa kita pada gagasan enkapsulasi yang sangat disayangi oleh desain OO.
Prinsip enkapsulasi sebenarnya adalah apa yang akan terjadi: sebagai desainer Anda harus menyembunyikan segala sesuatu tentang implementasi dan internal kelas yang tidak sepenuhnya harus diakses oleh pengguna atau pengembang lain. Karena kita menangani instance kelas, ini direduksi menjadi "operasi apa yang penting pada instance ini ". Jika suatu operasi tidak spesifik instance, maka itu seharusnya bukan fungsi anggota.
TL; DR : apa yang dikatakan @Bryan. Jika beroperasi pada sebuah instance dan perlu mengakses data yang internal ke instance kelas, itu harus menjadi fungsi anggota.
sumber
Gunakan kelas jika Anda ingin:
1) Pisahkan kode panggilan dari detail implementasi - manfaatkan abstraksi dan enkapsulasi .
2) Bila Anda ingin menggantikan objek lain - memanfaatkan polimorfisme .
3) Bila Anda ingin menggunakan kembali kode untuk objek serupa - memanfaatkan warisan .
Gunakan fungsi untuk panggilan yang masuk akal di berbagai tipe objek - misalnya, fungsi len dan repr bawaan berlaku untuk berbagai jenis objek.
Meski begitu, pilihan terkadang bergantung pada selera. Pikirkan tentang apa yang paling nyaman dan mudah dibaca untuk panggilan biasa. Misalnya, mana yang lebih baik
(x.sin()**2 + y.cos()**2).sqrt()
atausqrt(sin(x)**2 + cos(y)**2)
?sumber
Berikut aturan praktisnya: jika kode bekerja pada satu contoh objek, gunakan metode. Lebih baik lagi: gunakan metode kecuali ada alasan kuat untuk menuliskannya sebagai fungsi.
Dalam contoh spesifik Anda, Anda ingin terlihat seperti ini:
Jangan terlalu memikirkannya. Selalu gunakan metode hingga Anda tiba di titik di mana Anda berkata kepada diri sendiri "tidak masuk akal menjadikan ini metode", dalam hal ini Anda dapat membuat fungsi.
sumber
len()
itu juga dapat dikatakan bahwa desainnya masuk akal, meskipun menurut saya fungsi tidak akan terlalu buruk di sana - kami hanya memiliki konvensi lain yanglen()
selalu harus mengembalikan integer (tetapi dengan semua masalah tambahan dari backcomp, saya juga tidak akan menganjurkan itu untuk python)Saya biasanya memikirkan objek seperti orang.
Atribut adalah nama orang, tinggi badan, ukuran sepatu, dll.
Metode dan fungsi adalah operasi yang dapat dilakukan orang tersebut.
Jika operasi dapat dilakukan oleh sembarang orang, tanpa memerlukan sesuatu yang unik untuk orang tertentu ini (dan tanpa mengubah apa pun pada orang tertentu ini), maka itu adalah fungsi dan harus ditulis seperti itu.
Jika suatu operasi menimpa orang tersebut (misalnya makan, berjalan, ...) atau memerlukan sesuatu yang unik untuk terlibat (seperti menari, menulis buku, ...), maka itu harus menjadi metode .
Tentu saja, tidak selalu mudah untuk menerjemahkan ini ke dalam objek tertentu yang sedang Anda kerjakan, tetapi menurut saya ini adalah cara yang baik untuk memikirkannya.
sumber
height(person)
, bukanperson.height
?are_married(person1, person2)
? Kueri ini sangat umum dan karenanya harus menjadi fungsi dan bukan metode.Umumnya saya menggunakan kelas untuk mengimplementasikan serangkaian kemampuan logis untuk beberapa hal , sehingga di sisa program saya, saya dapat bernalar tentang hal itu , tidak perlu khawatir tentang semua masalah kecil yang membentuk implementasinya.
Apa pun yang merupakan bagian dari abstraksi inti "apa yang dapat Anda lakukan dengan sesuatu " biasanya harus menjadi sebuah metode. Ini umumnya mencakup segala sesuatu yang dapat mengubah sesuatu , karena status data internal biasanya dianggap pribadi dan bukan bagian dari gagasan logis "apa yang dapat Anda lakukan dengan sesuatu ".
Ketika Anda datang ke operasi tingkat yang lebih tinggi, terutama jika mereka melibatkan banyak hal , saya menemukan mereka biasanya paling alami diekspresikan sebagai fungsi, jika mereka dapat dibangun dari abstraksi publik sesuatu tanpa memerlukan akses khusus ke internal (kecuali mereka ' re metode dari beberapa objek lain). Ini memiliki keuntungan besar bahwa ketika saya memutuskan untuk sepenuhnya menulis ulang internal tentang cara kerja barang saya (tanpa mengubah antarmuka), saya hanya memiliki serangkaian metode inti kecil untuk menulis ulang, dan kemudian semua fungsi eksternal ditulis dalam istilah metode tersebut hanya akan Bekerja. Saya menemukan bahwa bersikeras bahwa semua operasi yang harus dilakukan dengan kelas X adalah metode pada kelas X mengarah ke kelas yang terlalu rumit.
Itu tergantung pada kode yang saya tulis. Untuk beberapa program, saya memodelkannya sebagai kumpulan objek yang interaksinya menimbulkan perilaku program; di sini fungsionalitas yang paling penting terkait erat dengan satu objek, dan begitu juga diimplementasikan dalam metode, dengan fungsi utilitas yang tersebar. Untuk program lain hal yang paling penting adalah seperangkat fungsi yang memanipulasi data, dan kelas-kelas yang digunakan hanya untuk mengimplementasikan "tipe bebek" alami yang dimanipulasi oleh fungsi.
sumber
Anda mungkin berkata bahwa, "dalam menghadapi ambiguitas, tolak godaan untuk menebak".
Namun, itu bahkan bukan tebakan. Anda benar-benar yakin bahwa hasil dari kedua pendekatan itu sama dalam memecahkan masalah Anda.
Saya percaya bahwa memiliki banyak cara untuk mencapai tujuan adalah hal yang baik. Dengan rendah hati saya akan memberi tahu Anda, seperti yang telah dilakukan pengguna lain, untuk menggunakan mana saja yang "terasa lebih enak" / terasa lebih intuitif , dalam hal bahasa.
sumber