Kapan saya harus menggunakan kelas dengan Python?

176

Saya telah memprogram dalam python selama sekitar dua tahun; sebagian besar data barang (panda, mpl, numpy), tetapi juga skrip otomatisasi dan aplikasi web kecil. Saya mencoba menjadi pemrogram yang lebih baik dan menambah pengetahuan python saya dan salah satu hal yang mengganggu saya adalah saya tidak pernah menggunakan kelas (di luar menyalin kode labu acak untuk aplikasi web kecil). Saya biasanya mengerti apa itu, tapi sepertinya saya tidak bisa memahami mengapa saya membutuhkannya daripada fungsi sederhana.

Untuk menambah kekhususan pada pertanyaan saya: Saya menulis banyak laporan otomatis yang selalu melibatkan penarikan data dari berbagai sumber data (mongo, sql, postgres, apis), melakukan banyak atau sedikit data, memformat dan memformat, menulis data ke csv / excel / html, kirimkan dalam email. Skrip berkisar dari ~ 250 baris hingga ~ 600 baris. Apakah ada alasan bagi saya untuk menggunakan kelas untuk melakukan ini dan mengapa?

meterk
sumber
15
tidak ada yang salah dengan kode tanpa kelas jika Anda dapat mengelola kode Anda lebih baik. Programmer OOP cenderung membesar-besarkan masalah karena kendala dari desain bahasa atau pemahaman dangkal pola yang berbeda.
Jason Hu

Jawaban:

133

Kelas adalah pilar Pemrograman Berorientasi Objek . OOP sangat memperhatikan organisasi kode, usabilitas ulang, dan enkapsulasi.

Pertama, penafian: OOP sebagian berbeda dengan Pemrograman Fungsional , yang merupakan paradigma yang berbeda banyak digunakan dalam Python. Tidak semua orang yang memprogram dalam Python (atau tentunya sebagian besar bahasa) menggunakan OOP. Anda dapat melakukan banyak hal di Java 8 yang tidak terlalu berorientasi objek. Jika Anda tidak ingin menggunakan OOP, maka jangan. Jika Anda hanya menulis skrip satu kali untuk memproses data yang tidak akan pernah Anda gunakan lagi, maka teruslah menulis seperti Anda.

Namun, ada banyak alasan untuk menggunakan OOP.

Beberapa alasan:

  • Organisasi: OOP mendefinisikan cara-cara yang dikenal dan standar untuk menggambarkan dan mendefinisikan data dan prosedur dalam kode. Baik data dan prosedur dapat disimpan pada berbagai tingkat definisi (di kelas yang berbeda), dan ada cara standar untuk membicarakan definisi ini. Yaitu, jika Anda menggunakan OOP dengan cara standar, itu akan membantu Anda nantinya dan orang lain memahami, mengedit, dan menggunakan kode Anda. Selain itu, alih-alih menggunakan mekanisme penyimpanan data yang rumit dan sewenang-wenang (dikte dicts atau daftar atau dicts atau daftar dicts set, atau apa pun), Anda dapat memberi nama potongan-potongan struktur data dan dengan mudah merujuknya.

  • Negara: OOP membantu Anda menentukan dan melacak keadaan. Misalnya, dalam contoh klasik, jika Anda membuat program yang memproses siswa (misalnya, program kelas), Anda dapat menyimpan semua informasi yang Anda butuhkan tentang mereka di satu tempat (nama, usia, jenis kelamin, tingkat kelas, kursus, nilai, guru, teman sebaya, diet, kebutuhan khusus, dll.), dan data ini bertahan selama objek masih hidup, dan mudah diakses.

  • Enkapsulasi : Dengan enkapsulasi, prosedur dan data disimpan bersama. Metode (istilah OOP untuk fungsi) didefinisikan tepat di samping data yang mereka operasikan dan hasilkan. Dalam bahasa seperti Java yang memungkinkan kontrol akses , atau dalam Python, tergantung pada bagaimana Anda menggambarkan API publik Anda, ini berarti bahwa metode dan data dapat disembunyikan dari pengguna. Apa artinya ini adalah bahwa jika Anda perlu atau ingin mengubah kode, Anda dapat melakukan apa pun yang Anda inginkan untuk implementasi kode, tetapi tetap menggunakan API publik.

  • Warisan : Warisan memungkinkan Anda untuk menentukan data dan prosedur di satu tempat (dalam satu kelas), lalu menimpa atau memperluas fungsionalitas itu nanti. Misalnya, dengan Python, saya sering melihat orang membuat subclass dictkelas untuk menambah fungsionalitas tambahan. Perubahan yang umum adalah mengganti metode yang melempar pengecualian ketika kunci diminta dari kamus yang tidak ada untuk memberikan nilai default berdasarkan kunci yang tidak dikenal. Ini memungkinkan Anda untuk memperluas kode Anda sendiri sekarang atau nanti, memungkinkan orang lain untuk memperluas kode Anda, dan memungkinkan Anda untuk memperluas kode orang lain.

  • Dapat digunakan kembali: Semua alasan ini dan lainnya memungkinkan penggunaan kembali kode yang lebih besar. Kode berorientasi objek memungkinkan Anda untuk menulis kode yang solid (diuji) sekali, dan kemudian menggunakan kembali berulang-ulang. Jika Anda perlu mengubah sesuatu untuk kasus penggunaan khusus Anda, Anda bisa mewarisi dari kelas yang ada dan menimpa perilaku yang ada. Jika Anda perlu mengubah sesuatu, Anda dapat mengubah semuanya sambil mempertahankan tanda tangan metode publik yang ada, dan tidak ada yang lebih bijak (mudah-mudahan).

Sekali lagi, ada beberapa alasan untuk tidak menggunakan OOP, dan Anda tidak perlu melakukannya. Tapi untungnya dengan bahasa seperti Python, Anda bisa menggunakan sedikit atau banyak, terserah Anda.

Contoh kasus penggunaan siswa (tidak ada jaminan kualitas kode, hanya sebuah contoh):

Berorientasi pada objek

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Dikte Standar

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))
dantiston
sumber
Karena "hasil" enkapsulasi Python seringkali lebih bersih dengan generator dan manajer konteks daripada dengan kelas.
Dmitry Rubanovich
4
@ meter saya menambahkan contoh. Saya harap ini membantu. Catatan di sini adalah bahwa alih-alih harus bergantung pada kunci dicts Anda yang memiliki nama yang benar, juru bahasa Python membuat batasan ini untuk Anda jika Anda mengacaukan dan memaksa Anda untuk menggunakan metode yang ditentukan (meskipun tidak didefinisikan bidang (meskipun Jawa dan lainnya) Bahasa OOP jangan biarkan Anda mendefinisikan bidang di luar kelas seperti Python)).
dantiston
5
@ meter juga, sebagai contoh enkapsulasi: katakanlah hari ini implementasi ini baik-baik saja karena saya hanya perlu mendapatkan IPK untuk 50.000 siswa di universitas saya sekali masa jabatan. Sekarang besok kita mendapatkan hibah dan perlu memberikan IPK saat ini dari setiap siswa setiap detik (tentu saja, tidak ada yang akan meminta ini, tetapi hanya untuk membuatnya menantang secara komputasi). Kami kemudian dapat "memoize" IPK dan hanya menghitungnya ketika itu berubah (misalnya, dengan menetapkan variabel dalam metode setGrade), yang lain mengembalikan versi yang di-cache. Pengguna masih menggunakan getGPA () tetapi implementasinya telah berubah.
dantiston
4
@ David, contoh ini membutuhkan collections.namedtuple. Anda dapat membuat tipe baru Student = collections.namedtuple ("Student", "nama, umur, jenis kelamin, level, nilai"). Dan kemudian Anda dapat membuat instance john = Student ("John", 12, "male", values ​​= {'math': 3.5}, level = 6). Perhatikan bahwa Anda menggunakan argumen posisional dan bernama seperti yang Anda lakukan dengan membuat kelas. Ini adalah tipe data yang sudah diterapkan untuk Anda dengan Python. Anda kemudian dapat merujuk ke john [0] atau john.name untuk mendapatkan elemen pertama dari tuple. Anda bisa mendapatkan nilai john sebagai john.grades.values ​​() sekarang. Dan itu sudah dilakukan untukmu.
Dmitry Rubanovich
2
Bagi saya enkapsulasi adalah alasan yang cukup baik untuk selalu menggunakan OOP. Saya kesulitan untuk melihat nilai BUKAN menggunakan OOP untuk proyek pengkodean berukuran cukup. Saya kira saya perlu jawaban untuk pertanyaan sebaliknya :)
San Jay
23

Setiap kali Anda perlu mempertahankan keadaan fungsi Anda dan itu tidak dapat diselesaikan dengan generator (fungsi yang menghasilkan daripada mengembalikan). Generator mempertahankan negara mereka sendiri.

Jika Anda ingin mengganti salah satu operator standar , Anda memerlukan kelas.

Setiap kali Anda menggunakan pola Pengunjung, Anda perlu kelas. Setiap pola desain lainnya dapat diselesaikan dengan lebih efektif dan bersih dengan generator, manajer konteks (yang juga lebih baik diimplementasikan sebagai generator daripada sebagai kelas) dan tipe POD (kamus, daftar dan tupel, dll.).

Jika Anda ingin menulis kode "pythonic", Anda harus memilih manajer konteks dan generator daripada kelas. Itu akan lebih bersih.

Jika Anda ingin memperluas fungsionalitas, Anda hampir selalu dapat mencapainya dengan penahanan daripada warisan.

Seperti setiap aturan, ini memiliki pengecualian. Jika Anda ingin merangkum fungsionalitas dengan cepat (yaitu, menulis kode uji alih-alih kode yang dapat digunakan kembali tingkat perpustakaan), Anda dapat merangkum keadaan dalam kelas. Ini akan sederhana dan tidak perlu digunakan kembali.

Jika Anda membutuhkan destruktor gaya C ++ (RIIA), Anda pasti TIDAK ingin menggunakan kelas. Anda ingin manajer konteks.

Dmitry Rubanovich
sumber
1
Penutupan @DmitryRubanovich tidak diterapkan melalui generator dengan Python.
Eli Korvigo
1
@ DmitryRubanovich saya mengacu pada "penutupan diimplementasikan sebagai generator di Python", yang tidak benar. Penutupan jauh lebih fleksibel. Generator terikat untuk mengembalikan Generatorinstance (iterator khusus), sementara penutupan dapat memiliki tanda tangan apa pun. Anda pada dasarnya dapat menghindari kelas sebagian besar waktu dengan membuat penutupan. Dan penutupan bukan hanya "fungsi yang didefinisikan dalam konteks fungsi lain".
Eli Korvigo
3
@Eli Korvigo, pada kenyataannya, generator adalah lompatan yang signifikan secara sintaksis. Mereka membuat abstraksi dari antrian dengan cara yang sama bahwa fungsinya adalah abstraksi dari stack. Dan sebagian besar aliran data dapat disatukan dari stack / antrian primitif.
Dmitry Rubanovich
1
@DmitryRubanovich kita berbicara apel dan jeruk di sini. Saya mengatakan, bahwa generator berguna dalam jumlah kasus yang sangat terbatas dan sama sekali tidak dapat dianggap sebagai pengganti untuk keperluan panggilan telepon yang sesuai dengan keperluan umum. Anda mengatakan kepada saya, betapa hebatnya mereka, tanpa bertentangan dengan poin saya.
Eli Korvigo
1
@Eli Korvigo, dan saya katakan bahwa callable hanya generalisasi fungsi. Gula sintaksis itu sendiri lebih dari pengolahan tumpukan. Sedangkan generator adalah gula sintaksis yang memproses antrian. Tetapi perbaikan dalam sintaks inilah yang memungkinkan konstruksi yang lebih rumit dibangun dengan mudah dan dengan sintaks yang lebih jelas. '.next ()' hampir tidak pernah digunakan, btw.
Dmitry Rubanovich
11

Saya pikir Anda melakukannya dengan benar. Kelas masuk akal ketika Anda perlu mensimulasikan beberapa logika bisnis atau proses kehidupan nyata yang sulit dengan hubungan yang sulit. Sebagai contoh:

  • Beberapa fungsi dengan status berbagi
  • Lebih dari satu salinan dari variabel keadaan yang sama
  • Untuk memperluas perilaku fungsi yang ada

Saya juga menyarankan Anda untuk menonton video klasik ini

valignatev
sumber
3
Tidak perlu menggunakan kelas ketika fungsi panggilan balik membutuhkan keadaan persisten dengan Python. Menggunakan hasil Python bukannya mengembalikan membuat fungsi kembali masuk.
Dmitry Rubanovich
4

Kelas mendefinisikan entitas dunia nyata. Jika Anda mengerjakan sesuatu yang ada secara individual dan memiliki logikanya sendiri yang terpisah dari yang lain, Anda harus membuat kelas untuk itu. Misalnya, kelas yang merangkum konektivitas basis data.

Jika bukan ini masalahnya, tidak perlu membuat kelas

Ashutosh
sumber
0

Tergantung pada ide dan desain Anda. jika Anda seorang desainer yang baik maka OOP akan keluar secara alami dalam bentuk berbagai pola desain. Untuk pemrosesan skrip tingkat sederhana, OOP bisa jadi overhead. Sederhana mempertimbangkan manfaat dasar OOP seperti dapat digunakan kembali dan dapat diperpanjang dan pastikan apakah mereka diperlukan atau tidak. OOP membuat hal-hal yang kompleks menjadi lebih sederhana dan hal-hal yang lebih sederhana menjadi rumit. Sederhananya hal-hal sederhana baik menggunakan OOP atau tidak Menggunakan OOP. mana yang lebih mudah menggunakannya.

Mohit Thakur
sumber