Hampir semua program dan teknik yang telah Anda gunakan sampai sekarang berada di bawah
gaya prosedural pemrograman. Memang, Anda telah menggunakan beberapa objek bawaan,
tetapi ketika merujuknya, kami hanya menyebutkan minimum absolut.
Pendekatan objek cukup muda (jauh lebih muda dari pendekatan prosedural) dan sangat
berguna ketika diterapkan pada proyek-proyek besar dan kompleks yang dilakukan oleh tim
besar yang terdiri dari banyak pengembang.
Pemahaman semacam ini tentang struktur proyek membuat banyak tugas penting lebih mudah,
misalnya, membagi proyek menjadi bagian-bagian kecil, mandiri, dan pengembangan
independen elemen-elemen proyek yang berbeda.
Python adalah alat universal untuk pemrograman objek dan prosedural . Ini mungkin berhasil
digunakan di kedua bidang.
Selain itu, Anda dapat membuat banyak aplikasi yang berguna, bahkan jika Anda tidak tahu
apa-apa tentang kelas dan objek, tetapi Anda harus ingat bahwa beberapa masalah (misalnya,
penanganan antarmuka pengguna grafis) mungkin memerlukan pendekatan objek yang ketat.
Untungnya, pemrograman objek relatif sederhana.
Fungsinya dapat menggunakan data, tetapi tidak sebaliknya. Selain itu, fungsi dapat
menyalahgunakan data, yaitu, untuk menggunakan nilai secara tidak sah (misalnya, ketika
fungsi sinus mendapatkan saldo rekening bank sebagai parameter).
Kami mengatakan di masa lalu bahwa data tidak dapat menggunakan fungsi. Tetapi apakah
ini sepenuhnya benar? Apakah ada beberapa jenis data khusus yang dapat menggunakan
fungsi?
Ya, ada - yang bernama metode. Ini adalah fungsi yang dipanggil dari dalam data, bukan di
sampingnya. Jika Anda dapat melihat perbedaan ini, Anda telah mengambil langkah pertama
ke pemrograman objek.
The pendekatan object menyarankan cara yang sama sekali berbeda pemikiran. Data dan
kode diapit bersama di dunia yang sama, dibagi menjadi beberapa kelas.
Setiap kelas seperti resep yang dapat digunakan ketika Anda ingin membuat objek yang
berguna (dari sinilah nama pendekatan berasal). Anda dapat menghasilkan objek sebanyak
yang Anda butuhkan untuk menyelesaikan masalah Anda.
Setiap objek memiliki seperangkat sifat (mereka disebut properti atau atribut - kami akan
menggunakan kedua kata secara sinonim) dan mampu melakukan serangkaian aktivitas (yang
disebut metode).
Resep dapat dimodifikasi jika tidak memadai untuk tujuan tertentu dan, pada dasarnya, kelas
baru dapat dibuat. Kelas-kelas baru ini mewarisi properti dan metode dari aslinya, dan biasanya
menambahkan beberapa yang baru, membuat alat baru yang lebih spesifik.
Objek adalah inkarnasi dari ide-ide yang diekspresikan dalam kelas, seperti cheesecake di piring
Anda adalah inkarnasi dari ide yang diekspresikan dalam resep yang dicetak dalam buku masak
lama.
Objek berinteraksi satu sama lain, bertukar data atau mengaktifkan metode mereka. Kelas yang
dibangun dengan benar (dan dengan demikian, objeknya) mampu melindungi data yang
masuk akal dan menyembunyikannya dari modifikasi yang tidak sah.
Tidak ada batas yang jelas antara data dan kode: mereka hidup sebagai satu di objek.
Semua konsep ini tidak abstrak seperti yang Anda duga pada awalnya. Sebaliknya, mereka
semua diambil dari pengalaman kehidupan nyata, dan karenanya sangat berguna dalam
pemrograman komputer: mereka tidak menciptakan kehidupan buatan - mereka
mencerminkan fakta, hubungan, dan keadaan nyata .
Kami akan mencoba menunjukkan beberapa kelas yang merupakan contoh bagus dari konsep
ini.
Mari kita lihat sejenak kendaraan. Semua kendaraan yang ada (dan yang belum
ada) dihubungkan oleh satu fitur penting : kemampuan untuk bergerak. Anda mungkin
berpendapat bahwa seekor anjing juga bergerak; apakah anjing itu kendaraan? Tidak. Kita
harus meningkatkan definisi, yaitu memperkaya dengan kriteria lain, membedakan kendaraan
dari makhluk lain, dan menciptakan koneksi yang lebih kuat. Mari kita pertimbangkan keadaan
berikut: kendaraan adalah entitas yang dibuat secara artifisial yang digunakan untuk
transportasi, digerakkan oleh kekuatan alam, dan diarahkan (didorong) oleh manusia.
Berdasarkan definisi ini, anjing bukan kendaraan.
Kelas kendaraan sangat luas. Terlalu luas Kita harus mendefinisikan beberapa kelas
yang lebih terspesialisasi. Kelas khusus adalah subclass. Kelas kendaraan akan
menjadi superclass bagi mereka semua.
Catatan: hierarki tumbuh dari atas ke bawah, seperti akar pohon, bukan cabang. Kelas yang
paling umum, dan terluas, selalu berada di atas (superclass) sedangkan turunannya berada di
bawah (subclass).
Dalam contoh ini, kita akan membahas hanya subkelas pertama - kendaraan darat. Jika mau,
Anda dapat melanjutkan dengan kelas yang tersisa.
Kendaraan darat dapat dibagi lebih lanjut, tergantung pada metode yang berdampak pada
tanah. Jadi, kita dapat menghitung:
kendaraan roda;
kendaraan yang dilacak;
hovercrafts.
Objek adalah inkarnasi dari persyaratan, sifat, dan kualitas yang ditugaskan untuk kelas
tertentu . Ini mungkin terdengar sederhana, tetapi perhatikan keadaan penting berikut ini. Kelas
membentuk hierarki. Ini dapat berarti bahwa objek milik kelas tertentu milik semua kacamata
super pada saat yang sama. Ini juga dapat berarti bahwa objek apa pun yang termasuk dalam
superclass mungkin bukan milik salah satu dari subkelasnya.
Sebagai contoh: setiap mobil pribadi adalah benda milik kelas kendaraan roda. Ini juga berarti
bahwa mobil yang sama milik semua superclasses dari kelas rumahnya; oleh karena itu, ia adalah
anggota kelas kendaraan juga. Anjing Anda (atau kucing Anda) adalah objek yang termasuk
dalam kelas mamalia peliharaan, yang secara eksplisit berarti ia termasuk
dalam kelas hewan juga.
Setiap subclass lebih terspesialisasi (atau lebih spesifik) daripada superclassnya. Sebaliknya,
setiap superclass lebih umum (lebih abstrak) daripada subkelasnya. Perhatikan bahwa kami
menduga bahwa sebuah kelas mungkin hanya memiliki satu superclass - ini tidak selalu benar,
tetapi kami akan membahas masalah ini sedikit lebih lanjut.
Warisan
Mari kita mendefinisikan salah satu konsep dasar pemrograman objek,
bernama inheritance. Objek apa pun yang terikat pada level tertentu dari hierarki kelas mewarisi
semua sifat (serta persyaratan dan kualitas) yang ditentukan di dalam salah satu superclasses.
Kelas asal objek dapat menentukan sifat-sifat baru (serta persyaratan dan kualitas) yang akan
diwarisi oleh salah satu superclasses-nya.
Anda seharusnya tidak memiliki masalah dengan mencocokkan aturan ini dengan contoh
spesifik, apakah itu berlaku untuk hewan, atau untuk kendaraan.
6.1.1.7 The foundations of OOP
Apa yang dimiliki objek?
Konvensi pemrograman objek mengasumsikan bahwa setiap objek yang ada dapat dilengkapi
dengan tiga kelompok atribut :
suatu objek memiliki nama yang secara unik mengidentifikasinya di dalam namespace
asalnya (walaupun mungkin ada beberapa objek anonim juga)
suatu objek memiliki sekumpulan properti individual yang membuatnya asli, unik atau luar
biasa (walaupun mungkin saja beberapa objek tidak memiliki properti sama sekali)
suatu objek memiliki seperangkat kemampuan untuk melakukan aktivitas tertentu,
mampu mengubah objek itu sendiri, atau beberapa objek lainnya.
Ada petunjuk (walaupun ini tidak selalu berhasil) yang dapat membantu Anda mengidentifikasi
salah satu dari tiga bidang di atas. Setiap kali Anda menggambarkan suatu objek dan Anda
menggunakan:
kata benda - Anda mungkin mendefinisikan nama objek;
kata sifat - Anda mungkin mendefinisikan properti objek;
kata kerja - Anda mungkin mendefinisikan aktivitas objek.
Kelas-kelas yang didefinisikan di awal terlalu umum dan tidak tepat untuk mencakup jumlah kasus
nyata yang paling besar.
Tidak ada halangan untuk mendefinisikan subclass baru yang lebih tepat. Mereka akan mewarisi
segalanya dari superclass mereka, sehingga pekerjaan yang masuk ke penciptaannya tidak sia-
sia.
Kelas baru dapat menambahkan properti baru dan aktivitas baru, dan karenanya mungkin lebih
berguna dalam aplikasi tertentu. Jelas, ini dapat digunakan sebagai superclass untuk sejumlah
subclass yang baru dibuat.
Prosesnya tidak harus berakhir. Anda dapat membuat kelas sebanyak yang Anda butuhkan.
Kelas yang Anda tetapkan tidak ada hubungannya dengan objek: keberadaan kelas tidak
berarti bahwa salah satu objek yang kompatibel akan secara otomatis dibuat. Kelas itu sendiri
tidak dapat membuat objek - Anda harus membuatnya sendiri, dan Python memungkinkan
Anda untuk melakukan ini.
Sudah waktunya untuk mendefinisikan kelas paling sederhana dan untuk membuat
objek. Lihatlah contoh di bawah ini:
class TheSimplestClass: pass
Kami telah mendefinisikan kelas di sana. Kelasnya agak buruk: tidak memiliki properti atau
aktivitas. Sebenarnya kosong, tapi itu tidak masalah untuk saat ini. Semakin sederhana kelas,
semakin baik untuk tujuan kita.
Definisi dimulai dengan kata kunciclass. Kata kunci diikuti oleh pengidentifikasi yang akan
memberi nama kelas (catatan: jangan bingung dengan nama objek - ini adalah dua hal yang
berbeda).
Selanjutnya, Anda menambahkan titik dua :), karena kelas, seperti fungsi, membentuk blok
bersarang mereka sendiri. Konten di dalam blok mendefinisikan semua properti dan aktivitas
kelas.
Kata passkunci tidak memenuhi kelas. Itu tidak mengandung metode atau properti apa pun.
myFirstObject = TheSimplestClass()
catatan:
nama kelas mencoba untuk berpura-pura bahwa itu adalah fungsi - dapatkah Anda
melihat ini? Kami akan segera membahasnya;
objek yang baru dibuat dilengkapi dengan semua yang dibawa oleh kelas; karena kelas
ini benar-benar kosong, objeknya juga kosong.
Tindakan membuat objek dari kelas yang dipilih juga disebut instantiation (karena objek
menjadi instance dari kelas ).
Mari kita tinggalkan kelas sendirian untuk sesaat, karena kita sekarang akan memberi tahu
Anda beberapa kata tentang tumpukan. Kita tahu konsep kelas dan objek mungkin
belum sepenuhnya jelas. Jangan khawatir, kami akan segera menjelaskan semuanya.
Nama alternatif untuk stack (tetapi hanya dalam terminologi IT) adalah LIFO . Ini adalah
singkatan dari deskripsi perilaku stack yang sangat jelas: Last In - First Out . Koin yang datang
terakhir ke tumpukan akan pergi terlebih dahulu.
Tumpukan adalah objek dengan dua operasi dasar, yang secara konvensional
bernama push(ketika elemen baru diletakkan di atas) dan pop (ketika elemen yang ada diambil
dari atas).
Tumpukan sangat sering digunakan dalam banyak algoritma klasik, dan sulit untuk
membayangkan implementasi dari banyak alat yang banyak digunakan tanpa menggunakan
tumpukan.
Mari kita mengimplementasikan tumpukan dengan Python. Ini akan menjadi tumpukan yang
sangat sederhana, dan kami akan menunjukkan kepada Anda bagaimana melakukannya
dalam dua pendekatan independen: prosedural dan objektif.
Mari kita mulai dengan yang pertama.
Kami siap untuk mendefinisikan fungsi yang memberikan nilai pada stack . Berikut adalah
anggapan untuk itu:
nama untuk fungsinya adalah push;
fungsi mendapat satu parameter (ini adalah nilai yang akan dimasukkan ke tumpukan)
fungsi tidak mengembalikan apa pun;
fungsi menambahkan nilai parameter ke akhir tumpukan;
Sekarang saatnya fungsi untuk mengambil nilai dari stack . Ini adalah bagaimana Anda dapat
melakukannya:
nama fungsinya adalah pop;
fungsi tidak mendapatkan parameter apa pun;
fungsi mengembalikan nilai yang diambil dari tumpukan
fungsi membaca nilai dari atas tumpukan dan menghilangkannya.
Fungsinya di sini:
def pop(): val = stack[-1] del stack[-1] return val
Menguji.
Tetapi semakin sering Anda menggunakannya, semakin banyak kerugian yang akan Anda
temui. Inilah beberapa di antaranya:
variabel esensial (daftar tumpukan) sangat rentan ; siapa pun dapat memodifikasinya
dengan cara yang tidak terkendali, menghancurkan tumpukan, pada dasarnya; ini
tidak berarti bahwa itu dilakukan dengan jahat - sebaliknya, itu bisa terjadi sebagai
akibat dari kecerobohan, misalnya, ketika seseorang membingungkan nama-nama
variabel; bayangkan bahwa Anda secara tidak sengaja telah menulis sesuatu seperti
ini:
stack[0] = 0
Fungsi tumpukan akan sepenuhnya berantakan;
mungkin juga terjadi bahwa suatu hari Anda membutuhkan lebih dari satu
tumpukan; Anda harus membuat daftar lain untuk penyimpanan stack ini, dan mungkin
lainnya pushdan popfungsi juga;
mungkin juga terjadi bahwa Anda tidak hanya membutuhkan pushdan popberfungsi,
tetapi juga beberapa kemudahan lain; Anda tentu bisa menerapkannya, tetapi coba
bayangkan apa yang akan terjadi jika Anda memiliki lusinan tumpukan yang
diimplementasikan secara terpisah.
Pendekatan objektif memberikan solusi untuk masing-masing masalah di atas. Beri nama dulu:
kemampuan untuk menyembunyikan (melindungi) nilai yang dipilih terhadap akses yang
tidak sah disebut enkapsulasi; nilai-nilai yang dienkapsulasi tidak dapat diakses atau
dimodifikasi jika Anda ingin menggunakannya secara eksklusif ;
ketika Anda memiliki kelas yang menerapkan semua perilaku stack yang diperlukan,
Anda dapat menghasilkan tumpukan sebanyak yang Anda inginkan; Anda tidak perlu
menyalin atau mereplikasi bagian mana pun dari kode;
kemampuan untuk memperkaya tumpukan dengan fungsi-fungsi baru berasal dari
warisan; Anda dapat membuat kelas baru (subkelas) yang mewarisi semua sifat yang
ada dari superclass, dan menambahkan beberapa yang baru.
Sekarang mari kita menulis implementasi stack baru dari awal. Kali ini, kami akan menggunakan
pendekatan objektif, membimbing Anda langkah demi langkah ke dunia pemrograman objek.
Sebagai gantinya, Anda perlu menambahkan pernyataan atau instruksi tertentu. Properti harus
ditambahkan ke kelas secara manual.
Bagaimana Anda menjamin bahwa kegiatan seperti itu terjadi setiap kali tumpukan baru dibuat?
Ada cara sederhana untuk melakukannya - Anda harus melengkapi kelas dengan fungsi
tertentu - kekhususannya ganda:
itu harus diberi nama dengan cara yang ketat;
itu dipanggil secara implisit, ketika objek baru dibuat.
Fungsi seperti ini disebut konstruktor , karena tujuan umumnya adalah untuk membangun objek
baru . Konstruktor harus tahu segalanya tentang struktur objek, dan harus melakukan semua
inisialisasi yang diperlukan.
Mari menambahkan konstruktor yang sangat sederhana ke kelas baru. Lihatlah cuplikannya:
class Stack: def __init__(self): print("Hi!") stackObject = Stack()
Dan sekarang:
nama konstruktor selalu __init__;
harus memiliki setidaknya satu parameter (kita akan membahas ini nanti); parameter
digunakan untuk mewakili objek yang baru dibuat - Anda dapat menggunakan
parameter untuk memanipulasi objek, dan untuk memperkaya dengan properti yang
dibutuhkan; Anda akan segera menggunakannya;
catatan: parameter wajib biasanya dinamai self- itu hanya konvensi, tetapi Anda harus
mengikutinya - itu menyederhanakan proses membaca dan memahami kode Anda.
Catatan - tidak ada jejak memohon konstruktor di dalam kode. Itu telah dipanggil secara implisit
dan otomatis. Mari kita manfaatkan itu sekarang.
Ini berarti Anda dapat menambahkan properti apa pun ke objek dan properti akan tetap di sana
sampai objek selesai masa pakainya atau properti secara eksplisit dihapus.
Sekarang mari kita tambahkan hanya satu properti ke objek baru - daftar untuk stack. Kami akan
beri nama stackList.
catatan:
kami telah menggunakan notasi bertitik , seperti saat menggunakan metode; ini adalah
konvensi umum untuk mengakses properti objek - Anda perlu memberi nama objek,
meletakkan titik ( .) setelahnya, dan menentukan nama properti yang diinginkan; jangan
gunakan tanda kurung! Anda tidak ingin menggunakan metode - Anda ingin mengakses
properti ;
jika Anda menetapkan nilai properti untuk pertama kalinya (seperti pada konstruktor),
Anda membuatnya; sejak saat itu, objek telah mendapatkan properti dan siap untuk
menggunakan nilainya;
kami telah melakukan sesuatu yang lebih dalam kode - kami telah mencoba
mengakses stackListproperti dari luar kelas segera setelah objek telah dibuat; kami ingin
memeriksa panjang tumpukan saat ini - apakah kami berhasil?
Ya, sudah - kode menghasilkan output berikut:
0
Ini bukan yang kita inginkan dari stack. Kami lebih memilih stackListuntuk disembunyikan dari
dunia luar . Apakah itu mungkin?
Ya, dan itu sederhana, tetapi tidak terlalu intuitif.
Kami ingin menjalankan fungsi pushdan popnilai ini. Ini berarti bahwa keduanya harus dapat
diakses oleh pengguna setiap kelas (berbeda dengan daftar yang dibuat sebelumnya, yang
disembunyikan dari pengguna kelas biasa).
Komponen seperti itu disebut publik , jadi Anda tidak dapat memulai namanya dengan dua
(atau lebih) garis bawah . Ada satu persyaratan lagi - nama harus memiliki tidak lebih dari satu
trailing garis bawah . Karena tidak ada jejak garis bawah yang sepenuhnya memenuhi
persyaratan, Anda dapat menganggap bahwa nama itu dapat diterima.
Namun, ada sesuatu yang sangat aneh dalam kode tersebut. Fungsi-fungsinya terlihat familier,
tetapi mereka memiliki lebih banyak parameter daripada rekan-rekan proseduralnya.
Di sini, kedua fungsi memiliki parameter bernama selfdi posisi pertama dari daftar parameter.
Apakah itu dibutuhkan? Ya itu.
Semua metode harus memiliki parameter ini. Ini memainkan peran yang sama dengan
parameter konstruktor pertama.
Ini memungkinkan metode untuk mengakses entitas (properti dan aktivitas / metode) yang
dilakukan oleh objek yang sebenarnya . Anda tidak bisa menghilangkannya. Setiap kali Python
memanggil metode, ia secara implisit mengirimkan objek saat ini sebagai argumen pertama.
Ini berarti bahwa suatu metode wajib memiliki setidaknya satu parameter, yang digunakan oleh
Python sendiri - Anda tidak memiliki pengaruh terhadapnya.
Jika metode Anda tidak memerlukan parameter sama sekali, ini harus tetap ditentukan. Jika
dirancang untuk memproses hanya satu parameter, Anda harus menentukan dua, dan peran
yang pertama masih sama.
Ada satu hal lagi yang membutuhkan penjelasan - cara di mana metode dipanggil dari
dalam __stackListvariabel.
Untungnya, ini jauh lebih sederhana daripada tampilannya:
tahap pertama mengirimkan objek secara keseluruhan → self;
selanjutnya, Anda perlu masuk ke __stackListdaftar → self.__stackList;
dengan __stackListsiap digunakan, Anda dapat melakukan langkah ketiga dan terakhir
→ self.__stackList.append(val).
Deklarasi kelas selesai, dan semua komponennya telah terdaftar. Kelas siap digunakan.
Ada dua tumpukan yang dibuat dari kelas dasar yang sama . Mereka bekerja secara
mandiri . Anda dapat membuat lebih banyak dari mereka jika Anda mau.
Jalankan kode di editor dan lihat apa yang terjadi. Lakukan eksperimen Anda sendiri.
Jadi, apa hasilnya? Jalankan program dan periksa apakah Anda benar.
Kelas baru harus dapat mengevaluasi jumlah semua elemen yang saat ini disimpan di stack .
Kami tidak ingin mengubah tumpukan yang ditentukan sebelumnya. Ini sudah cukup baik dalam
aplikasinya, dan kami tidak ingin itu diubah dengan cara apa pun. Kami ingin tumpukan baru
dengan kemampuan baru. Dengan kata lain, kami ingin membuat subkelas dari kelas yang
sudah ada Stack.
Langkah pertama mudah: cukup mendefinisikan subclass baru yang menunjuk ke kelas yang
akan digunakan sebagai superclass .
Seperti inilah tampilannya:
class AddingStack(Stack): pass
Kelas belum mendefinisikan komponen baru, tetapi itu tidak berarti bahwa itu kosong. Ia
mendapatkan semua komponen yang ditentukan oleh superclass - nama superclass ditulis
setelah titik dua langsung setelah nama kelas yang baru.
Inilah yang kami inginkan dari tumpukan baru:
kami ingin pushmetode ini tidak hanya untuk mendorong nilai ke stack tetapi juga untuk
menambahkan nilai ke sumvariabel;
kami ingin popfungsi tidak hanya mengeluarkan nilai dari tumpukan tetapi juga untuk
mengurangi nilai dari sumvariabel.
Pertama, mari kita tambahkan variabel baru ke kelas. Ini akan menjadi variabel pribadi , seperti
daftar tumpukan. Kami tidak ingin ada yang memanipulasi sumnilai.
Seperti yang sudah Anda ketahui, menambahkan properti baru ke kelas dilakukan oleh
konstruktor. Anda sudah tahu bagaimana melakukan itu, tetapi ada sesuatu yang sangat
menarik di dalam konstruktor. Lihatlah:
class AddingStack(Stack): def __init__(self): Stack.__init__(self) self.__sum = 0
Baris kedua tubuh konstruktor membuat properti bernama __sum- itu akan menyimpan total
semua nilai stack.
Tapi garis di depannya terlihat berbeda. Apa fungsinya? Apakah ini benar-benar perlu? Ya itu.
Berlawanan dengan banyak bahasa lain, Python memaksa Anda untuk secara eksplisit
memanggil konstruktor superclass . Menghilangkan titik ini akan memiliki efek berbahaya - objek
akan dicabut dari __stackListdaftar. Tumpukan seperti itu tidak akan berfungsi dengan baik.
Ini adalah satu-satunya waktu Anda dapat memanggil konstruktor yang tersedia secara eksplisit
- itu dapat dilakukan di dalam konstruktor superclass.
Perhatikan sintaks:
Anda menentukan nama superclass (ini adalah kelas yang konstruktornya ingin Anda
jalankan)
Anda menaruh titik ( .) setelahnya;
Anda menentukan nama konstruktor;
Anda harus menunjuk ke objek (instance kelas) yang harus diinisialisasi oleh konstruktor -
ini sebabnya Anda harus menentukan argumen dan menggunakan selfvariabel di
sini; Catatan: memohon metode apa pun (termasuk konstruktor) dari luar kelas tidak
pernah mengharuskan Anda untuk menempatkan selfargumen di daftar argumen-
memohon metode dari dalam kelas menuntut penggunaan eksplisit dari selfargumen,
dan itu harus diletakkan pertama pada daftar.
Catatan: umumnya merupakan praktik yang disarankan untuk memanggil konstruktor superclass
sebelum inisialisasi lain yang ingin Anda lakukan di dalam subkelas. Ini adalah aturan yang kami
ikuti dalam cuplikan.
Ya kita bisa. Itu berarti bahwa kita akan mengubah fungsionalitas metode, bukan nama
mereka . Kita dapat mengatakan lebih tepat bahwa antarmuka (cara menangani objek) dari
kelas tetap sama ketika mengubah implementasi pada saat yang sama.
Mari kita mulai dengan implementasi pushfungsi. Inilah yang kami harapkan darinya:
untuk menambahkan nilai ke __sumvariabel;
untuk mendorong nilai ke tumpukan.
Catatan: aktivitas kedua sudah diterapkan di dalam superclass - jadi kita bisa
menggunakannya. Selanjutnya, kita harus menggunakannya, karena tidak ada cara lain untuk
mengakses __stackListvariabel.
Sejauh ini, kami telah mendefinisikan __sumvariabel, tetapi kami belum menyediakan metode
untuk mendapatkan nilainya. Tampaknya disembunyikan. Bagaimana kita bisa
mengungkapkannya dan melakukannya dengan cara yang masih melindunginya dari
modifikasi?
Kita harus mendefinisikan metode baru. Kami akan beri nama getSum. Satu-satunya tugas
adalah mengembalikan __sumnilai .
Ini dia:
def getSum(self): return self.__sum
Jadi, mari kita lihat program di editor. Kode lengkap kelas ada di sana. Kami dapat memeriksa
fungsinya sekarang, dan kami melakukannya dengan bantuan beberapa baris kode tambahan.
Seperti yang Anda lihat, kami menambahkan lima nilai berikutnya ke tumpukan, mencetak
jumlahnya, dan mengambil semuanya dari tumpukan.
Oke, ini merupakan pengantar yang sangat singkat untuk pemrograman objek Python. Kami
akan segera memberi tahu Anda tentang hal itu secara lebih rinci.
Jenis properti kelas ini ada ketika dan hanya ketika itu secara eksplisit dibuat dan ditambahkan
ke objek. Seperti yang sudah Anda ketahui, ini dapat dilakukan selama inisialisasi objek, dilakukan
oleh konstruktor.
Selain itu, dapat dilakukan kapan saja dalam kehidupan objek. Lebih lanjut, setiap properti yang
ada dapat dihapus kapan saja.
Pendekatan semacam itu memiliki beberapa konsekuensi penting:
objek yang berbeda dari kelas yang sama dapat memiliki set properti yang berbeda ;
harus ada cara untuk memeriksa dengan aman apakah objek tertentu memiliki
propertiyang ingin Anda manfaatkan (kecuali jika Anda ingin memprovokasi
pengecualian - itu selalu layak dipertimbangkan)
setiap objek membawa set propertinya sendiri - mereka tidak saling mengganggu
dengan cara apa pun.
class ExampleClass: def __init__(self, val = 1): self.first = val def setSecond(self, val): self.second =
val exampleObject1 = ExampleClass() exampleObject2 = ExampleClass(2)
exampleObject2.setSecond(3) exampleObject3 = ExampleClass(4) exampleObject3.third = 5
print(exampleObject1.__dict__) print(exampleObject2.__dict__) print(exampleObject3.__dict__)
Perlu satu penjelasan tambahan sebelum kita membahas lebih detail. Lihatlah tiga baris terakhir
dari kode.
Objek python, ketika dibuat, diberikan kumpulan kecil properti dan metode yang telah
ditentukan . Setiap objek telah mendapatkannya, apakah Anda menginginkannya atau
tidak. Salah satunya adalah variabel bernama __dict__(ini kamus).
Variabel berisi nama dan nilai dari semua properti (variabel) objek saat ini membawa. Mari kita
gunakan untuk menyajikan konten objek dengan aman.
Output program jelas menunjukkan bahwa asumsi kami benar - ini dia:
{'first': 1} {'second': 3, 'first': 2} {'third': 5, 'first': 4}
Ada satu kesimpulan tambahan yang harus dinyatakan di sini: memodifikasi variabel instan dari
objek apa pun tidak memiliki dampak pada semua objek yang tersisa . Variabel instan
sepenuhnya terisolasi satu sama lain.
Perilaku sebenarnya dari nama-nama ini sedikit lebih rumit, jadi mari kita jalankan programnya. Ini
adalah output:
{'_ExampleClass__first': 1} {'_ExampleClass__first': 2, '_ExampleClass__second': 3}
{'_ExampleClass__first': 4, '__third': 5}
Bisakah Anda melihat nama-nama aneh ini penuh dengan garis bawah? Dari mana mereka
berasal?
Ketika Python melihat bahwa Anda ingin menambahkan variabel instan ke objek dan Anda akan
melakukannya di dalam salah satu metode objek, itu mengacaukan operasi dengan cara
berikut:
itu menempatkan nama kelas di depan nama Anda;
itu menempatkan garis bawah tambahan di awal.
Inilah sebabnya mengapa __firstmenjadi _ExampleClass__first.
Nama tersebut sekarang dapat diakses sepenuhnya dari luar kelas . Anda dapat menjalankan
kode seperti ini:
print(exampleObject1._ExampleClass__first)
dan Anda akan mendapatkan hasil yang valid tanpa kesalahan atau pengecualian.
Seperti yang Anda lihat, menjadikan properti pribadi terbatas.
Mangling tidak akan berfungsi jika Anda menambahkan variabel instan di luar kode
kelas . Dalam hal ini, ia akan berperilaku seperti properti biasa lainnya.
Catatan: tidak ada variabel instance jika tidak ada objek di kelas; variabel kelas ada dalam satu
salinan bahkan jika tidak ada objek di kelas.
Variabel kelas dibuat secara berbeda untuk saudara kandungnya. Contohnya akan memberi
tahu Anda lebih banyak:
class ExampleClass: counter = 0 def __init__(self, val = 1): self.__first = val ExampleClass.counter +=
1 exampleObject1 = ExampleClass() exampleObject2 = ExampleClass(2) exampleObject3 =
ExampleClass(4) print(exampleObject1.__dict__, exampleObject1.counter)
print(exampleObject2.__dict__, exampleObject2.counter) print(exampleObject3.__dict__,
exampleObject3.counter)
Melihat:
ada tugas di daftar pertama definisi kelas - itu menetapkan variabel
bernama counterke 0 ; menginisialisasi variabel di dalam kelas tetapi di luar salah satu
metodenya menjadikan variabel sebagai variabel kelas;
mengakses variabel seperti itu terlihat sama dengan mengakses atribut instance - Anda
dapat melihatnya di badan konstruktor; seperti yang Anda lihat, konstruktor menambah
variabel dengan satu; berlaku, variabel menghitung semua objek yang dibuat.
Sekarang kita akan mengambil kesempatan untuk menunjukkan kepada Anda perbedaan
antara dua __dict__variabel ini , satu dari kelas dan satu dari objek.
Lihatlah kode di editor. Buktinya ada di sana.
Mari kita lihat lebih dekat:
kami mendefinisikan satu kelas bernama ExampleClass;
kelas mendefinisikan satu variabel kelas bernama varia;
konstruktor kelas menetapkan variabel dengan nilai parameter;
memberi nama variabel adalah aspek yang paling penting dari contoh karena:
o mengubah tugas menjadi self.varia = valakan membuat variabel instan dengan
nama yang sama dengan yang ada di kelas;
o mengubah penugasan untuk varia = valakan beroperasi pada variabel lokal
metode; (kami sangat menganjurkan Anda untuk menguji kedua kasus di atas -
ini akan membuat Anda lebih mudah mengingat perbedaannya)
baris pertama kode off-class mencetak nilai ExampleClass.variaatribut; note - kita
menggunakan nilai sebelum objek pertama dari kelas dipakai.
Seperti yang Anda lihat, mengakses atribut objek (kelas) yang tidak ada
menyebabkan pengecualian AttributeError .
Seperti yang Anda lihat, tindakan ini tidak terlalu canggih. Intinya, kami baru saja menyapu
masalah di bawah karpet.
Untungnya, ada satu cara lagi untuk mengatasi masalah ini.
Python menyediakan fungsi yang dapat dengan aman memeriksa apakah ada objek / kelas
yang mengandung properti yang ditentukan . Fungsi ini dinamai hasattr, dan mengharapkan
dua argumen untuk diteruskan ke sana:
kelas atau objek yang diperiksa;
nama properti yang keberadaannya harus dilaporkan (catatan: harus berupa string yang
berisi nama atribut, bukan nama saja)
Fungsi mengembalikan True jika kelas yang ditentukan berisi atribut yang diberikan,
dan False sebaliknya.
Bisakah Anda menebak output kode? Jalankan untuk memeriksa tebakan Anda.
Dan satu contoh lagi - lihat kode di bawah ini dan coba prediksi hasilnya:
class ExampleClass: a = 1 def __init__(self): self.b = 2 exampleObject = ExampleClass()
print(hasattr(exampleObject, 'b')) print(hasattr(exampleObject, 'a')) print(hasattr(ExampleClass,
'b')) print(hasattr(ExampleClass, 'a'))
Seperti yang sudah Anda ketahui, metode adalah fungsi yang tertanam di dalam kelas .
Ada satu persyaratan mendasar - suatu metode wajib memiliki setidaknya satu parameter (tidak
ada yang namanya metode tanpa parameter - metode dapat dipanggil tanpa argumen, tetapi
tidak dideklarasikan tanpa parameter).
Parameter pertama (atau hanya) biasanya dinamai self. Kami menyarankan Anda mengikuti
konvensi - ini biasa digunakan, dan Anda akan menimbulkan beberapa kejutan dengan
menggunakan nama lain untuk itu.
Nama diri menyarankan tujuan parameter - itu mengidentifikasi objek yang metode ini
dipanggil .
Jika Anda akan memanggil metode, Anda tidak boleh melewatkan argumen
untuk selfparameter - Python akan mengaturnya untuk Anda.
Contoh di editor menunjukkan perbedaan.
Output kode:
method
Perhatikan cara kami membuat objek - kami telah memperlakukan nama kelas seperti fungsi ,
mengembalikan objek kelas yang baru dipakai.
Jika Anda ingin metode menerima parameter selain self, Anda harus:
tempatkan mereka setelah selfdalam definisi metode;
mengirimkannya selama doa tanpa menentukan self(seperti sebelumnya)
Sama seperti di sini:
class Classy: def method(self, par): print("method:", par) obj = Classy() obj.method(1)
obj.method(2) obj.method(3)
Output kode:
method: 1 method: 2 method: 3
6.1.4.2 OOP: Methods
Metode secara terperinci: lanjutan
The selfparameter digunakan untuk mendapatkan akses ke instance dan kelas objek variabel .
Contoh menunjukkan kedua cara memanfaatkan self:
class Classy: varia = 2 def method(self): print(self.varia, self.var) obj = Classy() obj.var = 3
obj.method()
Output kode:
23
The selfparameter juga digunakan untuk memanggil metode objek / kelas yang lain dari dalam
kelas .
Sama seperti di sini:
class Classy: def other(self): print("other") def method(self): print("method") self.other() obj =
Classy() obj.method()
Output kode:
method other
Jika kelas memiliki konstruktor, itu dipanggil secara otomatis dan implisit ketika objek kelas dipakai.
Konstruktor:
adalah wajib untuk memiliki selfparameter (itu diatur secara otomatis, seperti biasa);
mungkin (tetapi tidak perlu) memiliki lebih banyak parameterdari sekadar self; jika ini
terjadi, cara di mana nama kelas digunakan untuk membuat objek harus
mencerminkan __init__definisi;
dapat digunakan untuk mengatur objek , yaitu, menginisialisasi keadaan internal dengan
benar, membuat variabel instance, instantiate objek lain jika keberadaannya diperlukan,
dll.
Lihatlah kode di editor. Contoh menunjukkan konstruktor yang sangat sederhana di tempat kerja.
Menjalankannya. Output kode:
object
Output kode:
visible failed hidden
Jika Anda ingin menemukan kelas objek tertentu , Anda bisa menggunakan fungsi
bernama type(), yang mampu (antara lain) untuk menemukan kelas yang telah digunakan
untuk instantiate objek apa pun.
Seperti yang Anda ketahui, setiap modul yang dinamai __main__sebenarnya bukan modul,
tetapi file saat ini sedang dijalankan .
Selain itu, kami akan menunjukkan kepada Anda bagaimana menggunakan atribut ini ketika
kami membahas aspek objektif pengecualian.
Catatan: kelas tanpa superclasses eksplisit menunjuk ke objek (kelas Python yang telah
ditentukan) sebagai leluhur langsungnya.
Dengan kata lain, Anda tidak perlu mengetahui definisi kelas / objek yang lengkap untuk
memanipulasi objek, karena objek dan / atau kelasnya mengandung metadata yang
memungkinkan Anda mengenali fitur-fiturnya selama eksekusi program.
Fungsi bernama incIntsI()mendapat objek dari kelas apa pun, memindai isinya untuk
menemukan semua atribut integer dengan nama yang dimulai dengan i , dan
menambahkannya dengan satu.
Mustahil? Tidak semuanya!
Itu saja!
Jika Anda menjalankan kode yang sama di komputer Anda, Anda akan melihat sesuatu yang
sangat mirip, meskipun angka heksadesimal (substring dimulai dengan 0x ) akan berbeda,
karena itu hanya pengenal objek internal yang digunakan oleh Python, dan tidak mungkin
bahwa itu akan muncul sama ketika kode yang sama dijalankan di lingkungan yang berbeda.
Seperti yang Anda lihat, hasil cetak di sini tidak benar-benar berguna, dan sesuatu yang lebih
spesifik, atau lebih cantik, mungkin lebih disukai.
Untungnya, Python menawarkan fungsi seperti itu.
The most important factor of the process is the relation between the superclass and all of its
subclasses (note: if B is a subclass of A and C is a subclass of B, this also means than C is a subclass
of A, as the relationship is fully transitive).
A very simple example of two-level inheritance is presented here:
class Vehicle: pass class LandVehicle(Vehicle): pass class TrackedVehicle(LandVehicle): pass
All the presented classes are empty for now, as we're going to show you how the mutual relations
between the super- and subclasses work. We'll fill them with contents soon.
We can say that:
The Vehicle class is the superclass for both the LandVehicle and TrackedVehicleclasses;
The LandVehiclekelas adalah subclass dari Vehicledan superclass
dari TrackedVehiclepada waktu yang sama;
The TrackedVehiclekelas adalah subclass dari kedua Vehicledan LandVehiclekelas.
Pengetahuan di atas berasal dari membaca kode (dengan kata lain, kita tahu karena kita bisa
melihatnya).
Apakah Python mengetahui hal yang sama? Apakah mungkin untuk bertanya kepada Python
tentang hal itu? Ya itu.
Mari kita lihat dalam aksi - ini mungkin akan mengejutkan Anda. Lihatlah kode di editor. Baca
dengan cermat.
Ada dua loop bersarang. Tujuannya adalah untuk memeriksa semua pasangan kelas yang
mungkin dipesan, dan untuk mencetak hasil pemeriksaan untuk menentukan apakah pasangan
cocok dengan hubungan subkelas-superclass .
Jalankan kodenya. Program menghasilkan output sebagai berikut:
True False False True True False True True True
Mari kita asumsikan bahwa Anda memiliki kue (misalnya, ketika argumen dilewatkan ke fungsi
Anda). Anda ingin tahu resep apa yang telah digunakan untuk
membuatnya. Mengapa? Karena Anda ingin tahu apa yang diharapkan dari itu, misalnya,
apakah mengandung kacang atau tidak, yang merupakan informasi penting bagi sebagian
orang.
Demikian pula, ini dapat menjadi sangat penting jika objek memiliki (atau tidak memiliki)
karakteristik tertentu. Dengan kata lain, apakah itu objek kelas tertentu atau tidak .
Fakta seperti itu dapat dideteksi oleh fungsi bernama isinstance():
isinstance(objectName, ClassName)
Fungsi mengembalikan True jika objek adalah turunan dari kelas, atau Falsesebaliknya.
Menjadi turunan dari sebuah kelas berarti bahwa objek (kue) telah disiapkan menggunakan
resep yang terkandung dalam kelas atau salah satu dari superclasses-nya .
Jangan lupa: jika sebuah subclass mengandung setidaknya peralatan yang sama dengan
superclasses-nya, itu berarti bahwa objek-objek dari subclass dapat melakukan hal yang sama
seperti objek-objek yang berasal dari superclass, ergo, ini adalah turunan dari kelas rumahnya
dan kacamata supernya.
Ayo kita coba. Analisis kode dalam editor.
Kami telah membuat tiga objek, satu untuk setiap kelas. Selanjutnya, menggunakan dua loop
bersarang, kami memeriksa semua pasangan kelas objek yang mungkin untuk mengetahui
apakah objek adalah instance dari kelas .
Jalankan kodenya.
Inilah yang kami dapatkan:
True False False True True False True True True
The iscek Operator apakah dua variabel ( objectOnedan objectTwodi sini) mengacu pada
objek yang sama .
Jangan lupa bahwa variabel tidak menyimpan objek itu sendiri, tetapi hanya pegangan yang
menunjuk ke memori Python internal .
Menetapkan nilai variabel objek ke variabel lain tidak menyalin objek, tetapi hanya
menangani. Inilah sebabnya mengapa operator seperti ismungkin sangat berguna dalam
keadaan tertentu.
Lihatlah kode di editor. Mari kita analisa:
ada kelas yang sangat sederhana yang dilengkapi dengan konstruktor sederhana, hanya
menciptakan satu properti. Kelas digunakan untuk instantiate dua objek. Yang pertama
kemudian ditugaskan ke variabel lain, dan valpropertinya bertambah satu.
setelah itu, isoperator diterapkan tiga kali untuk memeriksa semua pasangan objek yang
mungkin, dan semua valnilai properti juga dicetak.
bagian terakhir dari kode melakukan percobaan lain. Setelah tiga penugasan, kedua
string berisi teks yang sama, tetapi teks-teks ini disimpan dalam objek yang berbeda .
Kode dicetak:
False False True 1 2 1 True False
Output kode:
My name is Andy.
Catatan: Karena tidak ada __str__()metode di dalam Subkelas, string yang dicetak akan
diproduksi di dalam Superkelas. Ini berarti bahwa __str__()metode ini telah diwarisi oleh Subkelas.
Dalam contoh terakhir, kami secara eksplisit menamai superclass. Dalam contoh ini, kami
menggunakan super()fungsi, yang mengakses superclass tanpa perlu tahu namanya :
super().__init__(name)
The super()Fungsi menciptakan konteks di mana Anda tidak perlu (apalagi, Anda harus tidak)
lulus argumen diri dengan metode yang dipanggil - ini adalah mengapa hal itu mungkin untuk
mengaktifkan konstruktor superclass hanya menggunakan satu argumen.
Catatan: Anda dapat menggunakan mekanisme ini tidak hanya untuk memanggil konstruktor
superclass, tetapi juga untuk mendapatkan akses ke sumber daya yang tersedia di dalam
superclass .
Seperti yang Anda lihat, Superkelas mendefinisikan satu variabel kelas bernama supVar,
dan Subkelas mendefinisikan variabel bernama subVar.
Kedua variabel ini terlihat di dalam objek kelas Sub- inilah sebabnya kode menghasilkan:
21
Saat Anda mencoba mengakses entitas objek apa pun, Python akan mencoba (dalam urutan
ini):
menemukannya di dalam objek itu sendiri;
temukan di semua kelas yangterlibat dalam garis warisan objek dari bawah ke atas;
Jika kedua hal di atas gagal, eksepsi ( AttributeError) dinaikkan .
Kondisi pertama mungkin perlu perhatian tambahan. Seperti yang Anda ketahui, semua objek
yang berasal dari kelas tertentu mungkin memiliki set atribut yang berbeda, dan beberapa
atribut dapat ditambahkan ke objek lama setelah penciptaan objek.
Contoh dalam editor merangkum ini dalam garis pewarisan tiga tingkat . Analisis dengan
cermat.
Semua komentar yang kami buat sejauh ini terkait dengan pewarisan tunggal , ketika subkelas
memiliki tepat satu superclass. Ini adalah situasi yang paling umum (dan yang direkomendasikan
juga).
Python, bagaimanapun, menawarkan lebih banyak di sini. Dalam pelajaran selanjutnya kami
akan menunjukkan beberapa contoh pewarisan berganda .
The Subkelas memiliki dua superclasses: SuperAdan SuperB. Ini berarti bahwa Subkelas mewarisi
semua barang yang ditawarkan oleh keduanya SuperAdanSuperB .
Kode dicetak:
10 11 20 21
Entitas yang didefinisikan kemudian (dalam pengertian pewarisan) menimpa entitas yang sama
yang didefinisikan sebelumnya . Inilah sebabnya mengapa kode menghasilkan output berikut:
200 201
Seperti yang Anda lihat, varvariabel kelas dan fun()metode dari Level2kelas menimpa entitas
dari nama yang sama yang berasal dari Level1kelas.
Fitur ini dapat dengan sengaja digunakan untuk memodifikasi perilaku kelas default (atau yang
didefinisikan sebelumnya) ketika salah satu kelasnya perlu bertindak dengan cara yang berbeda
dengan leluhurnya.
Kita juga dapat mengatakan bahwa Python mencari entitas dari bawah ke atas , dan
sepenuhnya puas dengan entitas pertama dari nama yang diinginkan.
Bagaimana cara kerjanya ketika sebuah kelas memiliki dua leluhur yang menawarkan entitas
yang sama, dan mereka berada pada level yang sama? Dengan kata lain, apa yang harus
Anda harapkan ketika sebuah kelas muncul menggunakan multiple inheritance? Mari kita lihat
ini.
Tidak ada keraguan bahwa variabel kelas varRightberasal dari Rightkelas, dan varLeftberasal
dari Leftmasing-masing.
Ini jelas. Tapi dari mana datangnya var? Apakah mungkin untuk menebaknya? Masalah yang
sama ditemui dengan fun()metode - apakah akan dipanggil dari Leftatau dari Right? Mari kita
jalankan program - outputnya adalah:
L LL RR Left
Ini membuktikan bahwa kedua kasus yang tidak jelas memiliki solusi di dalam Leftkelas. Apakah
ini premis yang cukup untuk merumuskan aturan umum? Ya itu.
Kita dapat mengatakan bahwa Python mencari komponen objek dalam urutan berikut:
di dalam objek itu sendiri;
dalam kacamata supernya , dari bawah ke atas;
jika ada lebih dari satu kelas pada jalur pewarisan tertentu, Python memindai mereka dari
kiri ke kanan.
Apakah Anda membutuhkan sesuatu yang lebih? Buat sedikit amandemen dalam kode -
ganti: class Sub(Left, Right):dengan class Sub(Right, Left)::, lalu jalankan kembali program, dan
lihat apa yang terjadi.
Apa yang kamu lihat sekarang? Kami melihat:
R LL RR Right
Apakah Anda melihat hal yang sama, atau sesuatu yang berbeda?
Pertanyaannya adalah - manakah dari dua metode yang akan dipanggil oleh dua baris terakhir
dari kode?
Doa kedua membutuhkan perhatian. Ini juga sederhana, jika Anda ingat bagaimana Python
menemukan komponen kelas. Doa kedua akan diluncurkan doit()dalam bentuk yang ada di
dalam Twokelas, terlepas dari fakta bahwa doa itu terjadi di dalam Onekelas.
Catatan: situasi di mana subclass dapat memodifikasi perilaku superclass-nya (seperti dalam
contoh) disebut polimorfisme . Kata ini berasal dari bahasa Yunani (polis: "banyak, banyak" dan
morphe, "bentuk, bentuk"), yang berarti bahwa satu dan kelas yang sama dapat mengambil
berbagai bentuk tergantung pada definisi ulang yang dilakukan oleh salah satu subkelasnya.
Metode, didefinisikan ulang di salah satu superclasses, sehingga mengubah perilaku superclass,
disebut virtual .
Dengan kata lain, tidak ada kelas yang diberikan sekali dan untuk semua. Perilaku setiap kelas
dapat dimodifikasi setiap saat oleh subkelasnya.
Kami akan menunjukkan kepada Anda bagaimana menggunakan polimorfisme untuk
memperluas fleksibilitas kelas .
Kami mendefinisikan dua kelas terpisah yang dapat menghasilkan dua jenis kendaraan
darat. Perbedaan utama di antara mereka adalah bagaimana mereka berubah. Kendaraan
roda hanya memutar roda depan (umumnya). Kendaraan yang dilacak harus menghentikan
salah satu lintasan.
Mari kita membangun kembali kode - kita akan memperkenalkan superclass untuk
mengumpulkan semua aspek yang sama dari kendaraan mengemudi, memindahkan semua
spesifik ke subclass.
Keuntungan yang paling penting (menghilangkan masalah keterbacaan) adalah bentuk kode
ini memungkinkan Anda untuk mengimplementasikan algoritma putaran baru hanya dengan
memodifikasi turn()metode, yang dapat dilakukan hanya di satu tempat, karena semua
kendaraan akan mematuhinya.
Beginilah polimorfisme membantu pengembang menjaga kode tetap bersih dan konsisten .
Subclass menerapkan kemampuan ini dengan memperkenalkan mekanisme khusus. Mari kita
lakukan (hampir) hal yang sama, tetapi menggunakan komposisi. Kelas - seperti dalam contoh
sebelumnya - menyadari cara membelokkan kendaraan, tetapi pergantian aktual dilakukan
oleh objek khusus yang disimpan di properti yang bernama controller. The controllermampu
mengendalikan kendaraan dengan memanipulasi bagian-bagian kendaraan yang
bersangkutan.
Ada dua kelas yang dinamai Tracksdan Wheels- mereka tahu cara mengendalikan arah
kendaraan. Ada juga kelas bernama Vehicleyang dapat menggunakan salah satu dari
pengontrol yang tersedia (dua sudah didefinisikan, atau lainnya didefinisikan di masa depan) -
kelas controlleritu sendiri diteruskan ke kelas selama inisialisasi.
Dengan cara ini, kemampuan kendaraan untuk berputar disusun menggunakan objek eksternal,
tidak diimplementasikan di dalam Vehiclekelas.
Dengan kata lain, kami memiliki kendaraan universal dan dapat memasang trek atau roda di
atasnya.
Hanya ada satu "tapi". Fakta bahwa Anda dapat melakukannya bukan berarti Anda harus
melakukannya.
Python, bagaimanapun, tidak suka berlian, dan tidak akan membiarkan Anda menerapkan hal
seperti ini. Jika Anda mencoba membangun hierarki seperti ini:
class A: pass class B(A): pass class C(A): pass class D(A, B): pass d = D()
di mana MROsingkatan dari Method Resolution Order - ini adalah algoritma yang digunakan
Python untuk mencari pohon warisan untuk menemukan metode yang diperlukan.
Berlian sangat berharga dan berharga ... tetapi tidak dalam pemrograman. Hindari mereka
untuk kebaikan Anda sendiri.
Sebelum kita terjun ke wajah objektif pengecualian , kami ingin menunjukkan kepada Anda
beberapa aspek sintaksis dan semantik dari cara Python memperlakukan blok coba-kecuali,
karena ia menawarkan sedikit lebih banyak dari apa yang telah kami sajikan sejauh ini.
Fitur pertama yang ingin kita diskusikan di sini adalah cabang tambahan yang memungkinkan
yang dapat ditempatkan di dalam (atau lebih tepatnya, tepat di belakang) blok coba-kecuali -
ini adalah bagian dari kode yang dimulai dengan else- seperti dalam contoh di editor.
Kode berlabel dengan cara ini dijalankan ketika (dan hanya ketika) tidak ada pengecualian
yang dimunculkan di dalam try:bagian tersebut. Kita dapat mengatakan bahwa tepat satu
cabang dapat dieksekusi setelah try:- baik yang dimulai dengan except(jangan lupa bahwa
mungkin ada lebih dari satu cabang semacam ini) atau yang dimulai dengan else.
Catatan: else:cabang harus ditempatkan setelah exceptcabang terakhir .
Catatan: dua varian ini ( elsedan finally) tidak tergantung dengan cara apa pun, dan mereka
dapat hidup berdampingan atau terjadi secara independen.
The finallyblock selalu dieksekusi (tim tersebut menyelesaikan try-kecuali eksekusi blok, maka
namanya), tidak peduli apa yang terjadi sebelumnya, bahkan ketika menaikkan pengecualian,
tidak peduli apakah ini sudah ditangani atau tidak.
Anda mungkin tidak akan terkejut mengetahui bahwa pengecualian adalah kelas . Lebih jauh
lagi, ketika sebuah eksepsi dimunculkan, objek kelas akan dipakai, dan melewati semua level
eksekusi program, mencari cabang kecuali yang siap untuk menghadapinya.
Objek seperti itu membawa beberapa informasi berguna yang dapat membantu Anda
mengidentifikasi dengan tepat semua aspek dari situasi yang tertunda. Untuk mencapai tujuan
itu, Python menawarkan varian khusus klausa pengecualian - Anda dapat menemukannya di
editor.
Seperti yang Anda lihat, exceptpernyataan diperluas, dan berisi frasa tambahan dimulai
dengan askata kunci, diikuti oleh pengenal. Pengidentifikasi dirancang untuk menangkap objek
pengecualian sehingga Anda dapat menganalisis sifatnya dan menarik kesimpulan yang tepat.
Contoh ini menyajikan cara yang sangat sederhana untuk menggunakan objek yang diterima -
cukup cetak saja (seperti yang Anda lihat, output dihasilkan oleh __str__()metode objek) dan
berisi pesan singkat yang menjelaskan alasannya.
Pesan yang sama akan dicetak jika tidak ada exceptblok pas dalam kode, dan Python terpaksa
menanganinya sendiri.
Karena pohon adalah contoh sempurna dari struktur data rekursif , rekursi tampaknya menjadi
alat terbaik untuk melewatinya. The printExcTree()Fungsi membutuhkan dua argumen:
titik di dalam pohon dari mana kita mulai melintasi pohon;
tingkat bersarang (kami akan menggunakannya untuk membangun gambar cabang
pohon yang disederhanakan)
Mari kita mulai dari akar pohon - akar dari kelas pengecualian Python
adalah BaseExceptionkelas (itu adalah superclass dari semua pengecualian lainnya).
Untuk setiap kelas yang ditemui, lakukan serangkaian operasi yang sama:
cetak namanya, diambil dari __name__properti;
iterate melalui daftar subclass yang dikirimkan oleh __subclasses__()metode, dan secara
rekursif memanggil printExcTree()fungsi, masing-masing menambah level nesting.
Perhatikan bagaimana kami menggambar cabang dan garpu. Cetakan tidak diurutkan
dengan cara apa pun - Anda dapat mencoba mengurutkannya sendiri, jika Anda
menginginkan tantangan. Selain itu, ada beberapa ketidakakuratan yang halus dalam
cara beberapa cabang disajikan. Itu bisa diperbaiki juga, jika Anda mau.
Begini tampilannya:
BaseException
+---Exception
| +---TypeError
| +---StopAsyncIteration
| +---StopIteration
| +---ImportError
| | +---ModuleNotFoundError
| | +---ZipImportError
| +---OSError
| | +---ConnectionError
| | | +---BrokenPipeError
| | | +---ConnectionAbortedError
| | | +---ConnectionRefusedError
| | | +---ConnectionResetError
| | +---BlockingIOError
| | +---ChildProcessError
| | +---FileExistsError
| | +---FileNotFoundError
| | +---IsADirectoryError
| | +---NotADirectoryError
| | +---InterruptedError
| | +---PermissionError
| | +---ProcessLookupError
| | +---TimeoutError
| | +---UnsupportedOperation
| | +---herror
| | +---gaierror
| | +---timeout
| | +---Error
| | | +---SameFileError
| | +---SpecialFileError
| | +---ExecError
| | +---ReadError
| +---EOFError
| +---RuntimeError
| | +---RecursionError
| | +---NotImplementedError
| | +---_DeadlockError
| | +---BrokenBarrierError
| +---NameError
| | +---UnboundLocalError
| +---AttributeError
| +---SyntaxError
| | +---IndentationError
| | | +---TabError
| +---LookupError
| | +---IndexError
| | +---KeyError
| | +---CodecRegistryError
| +---ValueError
| | +---UnicodeError
| | | +---UnicodeEncodeError
| | | +---UnicodeDecodeError
| | | +---UnicodeTranslateError
| | +---UnsupportedOperation
| +---AssertionError
| +---ArithmeticError
| | +---FloatingPointError
| | +---OverflowError
| | +---ZeroDivisionError
| +---SystemError
| | +---CodecRegistryError
| +---ReferenceError
| +---BufferError
| +---MemoryError
| +---Warning
| | +---UserWarning
| | +---DeprecationWarning
| | +---PendingDeprecationWarning
| | +---SyntaxWarning
| | +---RuntimeWarning
| | +---FutureWarning
| | +---ImportWarning
| | +---UnicodeWarning
| | +---BytesWarning
| | +---ResourceWarning
| +---error
| +---Verbose
| +---Error
| +---TokenError
| +---StopTokenizing
| +---Empty
| +---Full
| +---_OptionError
| +---TclError
| +---SubprocessError
| | +---CalledProcessError
| | +---TimeoutExpired
| +---Error
| | +---NoSectionError
| | +---DuplicateSectionError
| | +---DuplicateOptionError
| | +---NoOptionError
| | +---InterpolationError
| | | +---InterpolationMissingOptionError
| | | +---InterpolationSyntaxError
| | | +---InterpolationDepthError
| | +---ParsingError
| | | +---MissingSectionHeaderError
| +---InvalidConfigType
| +---InvalidConfigSet
| +---InvalidFgBg
| +---InvalidTheme
| +---EndOfBlock
| +---BdbQuit
| +---error
| +---_Stop
| +---PickleError
| | +---PicklingError
| | +---UnpicklingError
| +---_GiveupOnSendfile
| +---error
| +---LZMAError
| +---RegistryError
| +---ErrorDuringImport
+---GeneratorExit
+---SystemExit
+---KeyboardInterrupt
The BaseExceptionkelas memperkenalkan properti bernama args. Ini adalah tuple yang
dirancang untuk mengumpulkan semua argumen yang diteruskan ke konstruktor kelas . Itu
kosong jika konstruk telah dipanggil tanpa argumen, atau berisi hanya satu elemen ketika
konstruktor mendapatkan satu argumen (kami tidak menghitung selfargumen di sini), dan
seterusnya.
Kami telah menyiapkan fungsi sederhana untuk mencetak argsproperti dengan cara yang
elegan. Anda dapat melihat fungsinya di editor.
Kami telah menggunakan fungsi ini untuk mencetak konten argsproperti dalam tiga kasus
berbeda, di mana pengecualian Exceptionkelas dimunculkan dalam tiga cara berbeda. Untuk
membuatnya lebih spektakuler, kami juga telah mencetak objek itu sendiri, bersama dengan
hasil __str__()doa.
Kasus pertama terlihat rutin - hanya ada nama Pengecualian setelah raisekata kunci. Ini berarti
bahwa objek dari kelas ini telah dibuat dengan cara yang paling rutin.
Kasus kedua dan ketiga mungkin terlihat sedikit aneh pada pandangan pertama, tetapi tidak
ada yang aneh di sini - ini hanya doa konstruktor. Dalam raisepernyataan kedua , konstruktor
dipanggil dengan satu argumen, dan di argumen ketiga, dengan dua.
Seperti yang Anda lihat, output program mencerminkan ini, menunjukkan konten argsproperti
yang sesuai:
: : my exception : my exception : my exception ('my', 'exception') : ('my', 'exception') : ('my',
'exception')
Ini mungkin berguna ketika Anda membuat modul kompleks yang mendeteksi kesalahan dan
menimbulkan pengecualian, dan Anda ingin pengecualian mudah dibedakan dari yang lain
yang dibawa oleh Python.
Ini dilakukan dengan mendefinisikan pengecualian baru Anda sendiri sebagai subclass yang
berasal dari yang sudah ditentukan sebelumnya .
Catatan: jika Anda ingin membuat pengecualian yang akan digunakan sebagai kasus khusus
pengecualian bawaan apa pun, turunkan dari satu ini saja. Jika Anda ingin membangun hierarki
Anda sendiri, dan tidak ingin itu terkait erat dengan pohon pengecualian Python, turunkan dari
salah satu kelas pengecualian teratas, seperti Pengecualian .
Bayangkan bahwa Anda telah membuat aritmatika baru, diperintah oleh hukum dan teorema
Anda sendiri. Jelas bahwa pembagian telah didefinisikan ulang juga, dan harus berperilaku
dengan cara yang berbeda dari pembagian rutin. Jelas juga bahwa divisi baru ini harus
mengeluarkan pengecualiannya sendiri, berbeda dari ZeroDivisionError bawaan , tetapi masuk
akal untuk mengasumsikan bahwa dalam beberapa keadaan, Anda (atau pengguna aritmatika
Anda) mungkin ingin memperlakukan semua divisi nol dengan cara yang sama.
Permintaan seperti ini dapat dipenuhi dengan cara yang disajikan dalam editor. Lihatlah
kodenya, dan mari kita menganalisisnya:
Kami telah menetapkan pengecualian kami sendiri, yang dinamai MyZeroDivisionError,
berasal dari bawaan ZeroDivisionError. Seperti yang Anda lihat, kami memutuskan untuk
tidak menambahkan komponen baru ke kelas.
Akibatnya, pengecualian kelas ini dapat - tergantung pada sudut pandang yang
diinginkan - diperlakukan seperti ZeroDivisionError polos , atau dianggap secara terpisah.
Fungsi dipanggil empat kali secara total, sedangkan dua doa pertama ditangani hanya
menggunakan satu exceptcabang (yang lebih umum) dan dua yang terakhir dengan
dua cabang yang berbeda, mampu membedakan pengecualian (jangan lupa: urutan
cabang membuat perbedaan mendasar!)
Misalnya, jika Anda bekerja pada sistem simulasi besar yang dimaksudkan untuk memodelkan
kegiatan restoran pizza, dapat diinginkan untuk membentuk hierarki pengecualian yang terpisah.
Anda bisa mulai membangunnya dengan mendefinisikan pengecualian umum sebagai kelas
dasar baru untuk pengecualian khusus lainnya. Kami telah melakukannya dengan cara berikut:
class PizzaError(Exception): def __init__(self, pizza, message): Exception.__init__(message)
self.pizza = pizza
Catatan: kami akan mengumpulkan informasi lebih spesifik di sini daripada Pengecualian biasa ,
sehingga konstruktor kami akan mengambil dua argumen:
satu menetapkan pizza sebagai subjek dari proses,
dan satu berisi deskripsi masalah yang kurang lebih tepat.
Seperti yang Anda lihat, kami meneruskan parameter kedua ke konstruktor superclass, dan
menyimpan yang pertama di dalam properti kami sendiri.
Masalah yang lebih spesifik (seperti kelebihan keju) dapat memerlukan pengecualian yang lebih
spesifik. Dimungkinkan untuk mengambil kelas baru dari kelas yang sudah didefinisikan PizzaError,
seperti yang telah kita lakukan di sini:
class TooMuchCheeseError(PizzaError): def __init__(self, pizza, cheese, message):
PizzaError._init__(self, pizza, message) self.cheese = cheese
Salah satunya muncul di dalam makePizza()fungsi ketika salah satu dari dua situasi yang salah ini
ditemukan: permintaan pizza yang salah, atau permintaan terlalu banyak keju.
catatan:
menghapus cabang yang dimulai dengan except TooMuchCheeseErrorakan
menyebabkan semua pengecualian yang muncul diklasifikasikan sebagai PizzaError;
menghapus cabang dimulai dengan except
PizzaErrorwillmenyebabkan TooMuchCheeseErrorpengecualian tetap tidak tertangani,
dan akan menyebabkan program berakhir.
Solusi sebelumnya, meskipun elegan dan efisien, memiliki satu kelemahan penting. Karena cara
mendeklarasikan konstruktor yang agak mudah, pengecualian baru tidak dapat digunakan apa
adanya, tanpa daftar lengkap dari argumen yang diperlukan.
Kami akan menghapus kelemahan ini dengan menetapkan nilai default untuk semua parameter
konstruktor . Lihatlah:
Sekarang, jika keadaan memungkinkan, dimungkinkan untuk menggunakan nama kelas saja.
Generator Python adalah bagian dari kode khusus yang dapat menghasilkan serangkaian nilai,
dan untuk mengontrol proses iterasi . Inilah sebabnya mengapa generator sering
disebut iterator , dan meskipun beberapa mungkin menemukan perbedaan yang sangat halus
antara keduanya, kami akan memperlakukan mereka sebagai satu.
Anda mungkin tidak menyadarinya, tetapi Anda telah menemukan generator berkali-kali
sebelumnya. Lihatlah cuplikan yang sangat sederhana:
for i in range(5): print(i)
Suatu fungsi mengembalikan satu, nilai yang didefinisikan dengan baik - mungkin merupakan
hasil dari evaluasi yang kurang lebih kompleks, misalnya, polinomial, dan dipanggil sekali - hanya
sekali.
Generator mengembalikan serangkaian nilai , dan secara umum, (secara implisit) dipanggil
lebih dari sekali.
Dalam contoh tersebut, range()generator dipanggil enam kali, memberikan lima nilai berikutnya
dari nol hingga empat, dan akhirnya menandakan bahwa rangkaian telah selesai.
Proses di atas sepenuhnya transparan. Mari kita beri penjelasan. Mari kita tunjukkan protokol
iterator .
Melihat:
objek iterator dipakai lebih dulu;
selanjutnya, Python memanggil __iter__metode untuk mendapatkan akses ke iterator
yang sebenarnya;
yang __next__metode dipanggil sebelas kali - yang pertama sepuluh kali menghasilkan
nilai-nilai yang bermanfaat, sedangkan kesebelas berakhir iterasi.
Kode ini tidak benar-benar canggih, tetapi menyajikan konsep dengan cara yang jelas.
Lihatlah kode di editor.
Kami telah membangun Fibiterator ke dalam kelas lain (kita dapat mengatakan bahwa kita
telah mengkomposisikannya ke dalam Classkelas). Ini dipakai bersama dengan Classobjek.
Objek kelas dapat digunakan sebagai iterator ketika (dan hanya ketika) itu menjawab
positif __iter__doa - kelas ini dapat melakukannya, dan jika itu dipanggil dengan cara ini, ia
menyediakan objek yang dapat mematuhi protokol iterasi.
Inilah sebabnya mengapa output kode sama dengan sebelumnya, meskipun objek Fibkelas
tidak digunakan secara eksplisit di dalam forkonteks loop.
Misalnya, Fibiterator dipaksa untuk secara tepat menyimpan tempat di mana doa terakhir telah
dihentikan (yaitu, jumlah yang dievaluasi dan nilai-nilai dari dua elemen sebelumnya). Ini
membuat kode lebih besar dan kurang dimengerti.
Inilah sebabnya mengapa Python menawarkan cara menulis iterator yang jauh lebih efektif,
nyaman, dan elegan.
Konsep ini pada dasarnya didasarkan pada mekanisme yang sangat spesifik dan kuat yang
disediakan oleh yieldkata kunci.
Anda mungkin menganggap yieldkata kunci sebagai saudara yang lebih cerdas
dari returnpernyataan itu, dengan satu perbedaan penting.
Lihatlah fungsi ini:
def fun(n): for i in range(n): return i
Terlihat aneh, bukan? Sudah jelas bahwa forloop tidak memiliki kesempatan untuk
menyelesaikan eksekusi pertama, karena returnakan merusaknya tidak dapat dibatalkan.
Selain itu, menjalankan fungsi tidak akan mengubah apa pun - forloop akan mulai dari awal dan
akan segera rusak.
Kita dapat mengatakan bahwa fungsi seperti itu tidak dapat menyimpan dan mengembalikan
kondisinya di antara permintaan berikutnya.
Ini juga berarti bahwa fungsi seperti ini tidak dapat digunakan sebagai generator .
Kami telah mengganti tepat satu kata dalam kode - dapatkah Anda melihatnya?
def fun(n): for i in range(n): yield i
Kami telah menambahkan yieldsebagai gantinya return. Amandemen kecil ini mengubah
fungsi menjadi generator , dan menjalankan yieldpernyataan memiliki beberapa efek yang
sangat menarik.
Pertama-tama, ini memberikan nilai ekspresi yang ditentukan setelah yieldkata kunci,
seperti return, tetapi tidak kehilangan status fungsi.
Semua nilai variabel dibekukan, dan tunggu permintaan berikutnya, ketika eksekusi dilanjutkan
(tidak diambil dari awal, seperti setelah return).
Ada satu batasan penting: fungsi seperti itu tidak boleh dipanggil secara eksplisit karena - pada
kenyataannya - itu bukan fungsi lagi; itu adalah objek generator .
Doa akan mengembalikan pengidentifikasi objek , bukan seri yang kita harapkan dari generator.
Karena alasan yang sama, fungsi sebelumnya (yang dengan returnpernyataan) hanya dapat
dipanggil secara eksplisit, dan tidak boleh digunakan sebagai generator.
The list()Fungsi dapat mengubah serangkaian doa Generator selanjutnya ke daftar nyata :
def powersOf2(n): pow = 1 for i in range(n): yield pow pow *= 2 t = list(powersOf2(3)) print(t)
Sekali lagi, cobalah untuk memprediksi output dan menjalankan kode untuk memeriksa prediksi
Anda.
Selain itu, konteks yang dibuat oleh inoperator memungkinkan Anda untuk menggunakan
generator juga.
Sekarang mari kita lihat generator angka Fibonacci , dan memastikan bahwa itu terlihat jauh
lebih baik daripada versi objektif berdasarkan implementasi protokol iterator langsung.
Ini dia:
def Fib(n): p = pp = 1 for i in range(n): if i in [0, 1]: yield 1 else: n = p + pp pp, p = p, n yield n fibs =
list(Fib(10)) print(fibs)
Tebak output (daftar) yang dihasilkan oleh generator, dan jalankan kode untuk memeriksa
apakah Anda benar.
Yang pertama menggunakan cara rutin memanfaatkan forloop, sedangkan yang terakhir
menggunakan daftar pemahaman dan membangun daftar di situ, tanpa perlu loop, atau kode
tambahan lainnya.
Sepertinya daftar dibuat di dalam dirinya sendiri - itu tidak benar, tentu saja, karena Python harus
melakukan operasi yang hampir sama seperti pada cuplikan pertama, tetapi tidak dapat
dibantah bahwa formalisme kedua hanya lebih elegan, dan memungkinkan pembaca
menghindari detail yang tidak perlu.
Contoh menghasilkan dua baris identik yang berisi teks berikut:
[1, 10, 100, 1000, 10000, 100000]
Ini adalah ekspresi kondisional - cara memilih salah satu dari dua nilai yang berbeda
berdasarkan hasil ekspresi Boolean .
Melihat:
expression_one jika syarat lain expression_two
Sekilas mungkin terlihat mengejutkan, tetapi Anda harus ingat bahwa ini bukan instruksi
bersyarat . Apalagi itu bukan instruksi sama sekali. Itu operator.
Contoh yang baik akan memberi tahu Anda lebih banyak. Lihatlah kode di editor.
Kode mengisi daftar dengan 1's dan 0s - jika indeks elemen tertentu aneh, elemen diatur ke 0,
dan 1sebaliknya.
Sekarang lihat kode di bawah ini dan lihat apakah Anda dapat menemukan detail yang
mengubah pemahaman daftar menjadi generator:
lst = [1 if x % 2 == 0 else 0 for x in range(10)] genr = (1 if x % 2 == 0 else 0 for x in range(10)) for v in
lst: print(v, end=" ") print() for v in genr: print(v, end=" ") print()
Bagaimana Anda bisa tahu bahwa tugas kedua membuat generator, bukan daftar?
Ada beberapa bukti yang bisa kami tunjukkan. Terapkan len()fungsi ke kedua entitas ini.
len(lst)akan dievaluasi untuk 10. Jelas dan dapat diprediksi. len(genr)akan memunculkan
pengecualian, dan Anda akan melihat pesan berikut:
TypeError: object of type 'generator' has no len()
Tentu saja, tidak perlu menyimpan daftar atau generator - Anda dapat membuatnya tepat di
tempat Anda membutuhkannya - seperti di sini:
for v in [1 if x % 2 == 0 else 0 for x in range(10)]: print(v, end=" ") print() for v in (1 if x % 2 == 0 else 0
for x in range(10)): print(v, end=" ") print()
Catatan: tampilan output yang sama tidak berarti bahwa kedua loop bekerja dengan cara yang
sama. Pada loop pertama, daftar dibuat (dan diulangi) secara keseluruhan - itu benar-benar
ada ketika loop sedang dieksekusi.
Pada loop kedua, tidak ada daftar sama sekali - hanya ada nilai selanjutnya yang dihasilkan oleh
generator, satu per satu.
Lakukan eksperimen Anda sendiri.
Matematikawan menggunakan kalkulus Lambda dalam banyak sistem formal yang terhubung
dengan logika, rekursi, atau teorema. Pemrogram menggunakan lambdafungsi untuk
menyederhanakan kode, untuk membuatnya lebih jelas dan lebih mudah dimengerti.
Sebuah lambdafungsi adalah fungsi tanpa nama (Anda juga dapat menyebutnya fungsi
anonim ). Tentu saja, pernyataan seperti itu langsung menimbulkan pertanyaan: bagaimana
Anda menggunakan sesuatu yang tidak dapat diidentifikasi?
Untungnya, ini bukan masalah, karena Anda dapat memberi nama fungsi semacam itu jika Anda
benar-benar membutuhkannya, tetapi, pada kenyataannya, dalam banyak
kasus, lambdafungsi tersebut dapat ada dan berfungsi sambil tetap menggunakan
penyamaran sepenuhnya.
Deklarasi lambdafungsi tidak menyerupai deklarasi fungsi normal dengan cara apa pun - lihat
sendiri:
lambda parameters : expression
Klausa seperti itu mengembalikan nilai ekspresi ketika memperhitungkan nilai saat ini
dari lambdaargumen saat ini .
Seperti biasa, sebuah contoh akan sangat membantu. Contoh kami menggunakan
tiga lambdafungsi, tetapi memberi mereka nama. Lihatlah dengan cermat:
two = lambda : 2 sqr = lambda x : x * x pwr = lambda x, y : x ** y for a in range(-2, 3): print(sqr(a),
end=" ") print(pwr(a, two()))
Contoh ini cukup jelas untuk menunjukkan bagaimana lambdas dideklarasikan dan bagaimana
mereka berperilaku, tetapi tidak mengatakan mengapa mereka diperlukan, dan untuk apa
mereka digunakan, karena semuanya dapat diganti dengan fungsi Python rutin.
Di mana manfaatnya?
Bayangkan bahwa kita membutuhkan suatu fungsi (kita akan menamainya printfunction) yang
mencetak nilai dari suatu fungsi (lainnya) yang diberikan untuk sekumpulan argumen yang dipilih.
Kami ingin printfunctionmenjadi universal - ia harus menerima serangkaian argumen yang
dimasukkan dalam daftar dan fungsi yang akan dievaluasi, baik sebagai argumen - kami tidak
ingin melakukan hardcode apa pun.
Catatan: kami juga mendefinisikan fungsi yang dinamai poly()- ini adalah fungsi yang nilainya
akan kami cetak. Penghitungan fungsi yang dijalankannya tidak terlalu canggih - itu adalah
polinomial (karena itu namanya) dari suatu bentuk:
f (x) = 2x 2 - 4x + 2
Nama fungsi kemudian diteruskan ke printfunction()bersama dengan satu set dari lima argumen
yang berbeda - set dibangun dengan klausa pemahaman daftar.
Bisakah kita menghindari mendefinisikan poly()fungsi, karena kita tidak akan menggunakannya
lebih dari sekali? Ya, kita bisa - inilah manfaat yang bisa diberikan lambda.
The printfunction()tetap persis sama, tetapi tidak ada poly()fungsi. Kita tidak perlu lagi, karena
jumlahnya banyak sekarang langsung di dalam printfunction()doa dalam bentuk lambda
didefinisikan dengan cara berikut: lambda x: 2 * x**2 - 4 * x + 2.
Kode menjadi lebih pendek, lebih jelas, dan lebih mudah dibaca.
Mari kita tunjukkan tempat lain di mana lambda bisa berguna. Kami akan mulai dengan
deskripsi map(), fungsi Python bawaan. Namanya tidak terlalu deskriptif, idenya sederhana, dan
fungsinya sendiri benar-benar dapat digunakan.
Inilah intriknya:
membangun list1dengan nilai-nilai dari 0ke 4;
selanjutnya, gunakan mapbersama dengan yang pertama lambdauntuk membuat
daftar baru di mana semua elemen telah dievaluasi sebagai 2dinaikkan ke daya yang
diambil dari elemen yang sesuai dari list1;
list2 dicetak kemudian;
pada langkah berikutnya, gunakan map()fungsi lagi untuk menggunakan generator
yang dikembalikan dan untuk langsung mencetak semua nilai yang diberikannya; seperti
yang Anda lihat, kami telah melibatkan yang kedua di lambdasini - itu hanya kuadrat
setiap elemen dari list2.
Coba bayangkan kode yang sama tanpa lambda. Apakah akan lebih baik? Tidak mungkin.
Itu mengharapkan jenis argumen yang sama seperti map(), tetapi melakukan sesuatu yang
berbeda - ia memfilter argumen kedua sambil dipandu oleh arah yang mengalir dari fungsi yang
ditentukan sebagai argumen pertama(fungsi dipanggil untuk setiap elemen daftar, seperti
di map()).
Elemen yang kembali Truedari fungsi lulus filter - yang lain ditolak.
Contoh di editor menunjukkan filter()fungsi dalam aksi.
Catatan: kami telah membuat penggunaan randommodul untuk menginisialisasi nomor acak
generator (tidak harus bingung dengan generator yang baru saja kita berbicara tentang)
dengan seed()fungsi, dan menghasilkan lima nilai integer acak dari -
10ke 10menggunakan randint()fungsi.
Daftar ini kemudian disaring, dan hanya angka-angka yang genap dan lebih besar dari nol yang
diterima.
Tentu saja, sepertinya Anda tidak akan menerima hasil yang sama, tetapi seperti inilah hasil kami:
[6, 3, 3, 2, -7] [6, 2]
Ada elemen baru di dalamnya - fungsi (bernama inner) di dalam fungsi lain (bernama outer).
Bagaimana cara kerjanya? Sama seperti fungsi lain kecuali untuk fakta yang inner()dapat
dipanggil hanya dari dalam outer(). Kita dapat mengatakan bahwa
itu inner()adalah outer()alat pribadi - tidak ada bagian lain dari kode yang dapat
mengaksesnya.
Perhatikan baik-baik:
yang inner()fungsi mengembalikan nilai diakses variabel di dalam ruang lingkup,
seperti inner()dapat menggunakan salah satu entitas di pembuangan outer()
yang outer()mengembalikan fungsi inner()fungsi itu sendiri; lebih tepatnya, itu
mengembalikan salinan inner()fungsi, yang dibekukan pada saat outer()doa; fungsi
beku berisi lingkungan penuhnya, termasuk keadaan semua variabel lokal, yang juga
berarti bahwa nilai locberhasil dipertahankan, meskipun sudah outer()tidak ada sejak
lama.
Akibatnya, kode ini sepenuhnya valid, dan menghasilkan:
1
yang inner()fungsi adalah parameterless, jadi kami harus memanggil tanpa argumen.
Sekarang lihat kode di editor. Sangat mungkin untuk mendeklarasikan penutupan yang
dilengkapi dengan sejumlah parameter yang berubah-ubah , misalnya, seperti power()fungsi.
Ini berarti bahwa penutupan tidak hanya memanfaatkan lingkungan beku, tetapi juga
dapat mengubah perilakunya dengan menggunakan nilai yang diambil dari luar .
Contoh ini menunjukkan satu keadaan yang lebih menarik - Anda dapat membuat penutupan
sebanyak yang Anda inginkan menggunakan satu dan bagian kode yang sama . Ini dilakukan
dengan fungsi bernama makeclosure(). catatan:
penutupan pertama yang diperoleh dari makeclosure()mendefinisikan alat
mengkuadratkan argumennya;
yang kedua dirancang untuk kubus argumen.
Jauh lebih sulit untuk membayangkan tugas yang sama ketika ada 20.000 angka untuk disortir,
dan tidak ada satu pengguna pun yang bisa memasukkan angka-angka ini tanpa membuat
kesalahan.
Jauh lebih mudah untuk membayangkan bahwa angka-angka ini disimpan dalam file disk yang
dibaca oleh program. Program ini mengurutkan angka-angka dan tidak mengirimnya ke layar,
tetapi sebaliknya membuat file baru dan menyimpan urutan angka yang terurut di sana.
Pada prinsipnya, masalah pemrograman yang tidak sederhana bergantung pada penggunaan
file, apakah itu memproses gambar (disimpan dalam file), mengalikan matriks (disimpan dalam
file), atau menghitung upah dan pajak (membaca data yang disimpan dalam file).
Anda mungkin bertanya mengapa kami menunggu sampai sekarang untuk menunjukkan
masalah ini kepada Anda.
Jika kita menggunakan gagasan nama file kanonik (nama yang secara unik mendefinisikan
lokasi file terlepas dari levelnya di pohon direktori) kita dapat menyadari bahwa nama-nama ini
terlihat berbeda di Windows dan di Unix / Linux:
Seperti yang Anda lihat, sistem yang berasal dari Unix / Linux tidak menggunakan huruf disk drive
(misalnya, C:) dan semua direktori tumbuh dari satu direktori root yang disebut /, sementara
sistem Windows mengenali direktori root sebagai \.
Selain itu, nama file sistem Unix / Linux adalah case-sensitive. Sistem Windows menyimpan huruf
yang digunakan dalam nama file, tetapi tidak membedakan antara hurufnya sama sekali.
Ini berarti bahwa dua string ini:
ThisIsTheNameOfTheFile
dan
thisisthenameofthefile
menjelaskan dua file berbeda di sistem Unix / Linux, tetapi nama yang sama untuk hanya satu
file di sistem Windows.
Perbedaan utama dan paling mencolok adalah Anda harus menggunakan dua pemisah yang
berbeda untuk nama direktori : \di Windows, dan /di Unix / Linux.
Perbedaan ini tidak terlalu penting bagi pengguna normal, tetapi sangat penting ketika menulis
program dengan Python .
Untuk memahami alasannya, cobalah untuk mengingat peran yang sangat spesifik yang
dimainkan oleh \string Python di dalam.
Anda akan mendapatkan kejutan yang tidak menyenangkan: baik Python akan menghasilkan
kesalahan, atau eksekusi program akan berperilaku aneh, seolah-olah nama file telah terdistorsi
dalam beberapa cara.
Sebenarnya, itu tidak aneh sama sekali, tetapi cukup jelas dan alami. Python menggunakan
karakter \sebagai pelarian (seperti \n).
Ini berarti bahwa nama file Windows harus ditulis sebagai berikut:
name = "\\dir\\file"
Untungnya, ada juga satu solusi lagi. Python cukup pintar untuk dapat mengubah garis miring
menjadi garis miring terbalik setiap kali ditemukan bahwa itu diperlukan oleh OS.
Dengan cara ini, Anda dapat menerapkan proses mengakses file apa pun, bahkan ketika nama
file tidak diketahui pada saat menulis program.
Operasi yang dilakukan dengan aliran abstrak mencerminkan aktivitas yang terkait dengan file
fisik.
Untuk menghubungkan (mengikat) aliran dengan file, perlu untuk melakukan operasi eksplisit.
Operasi menghubungkan aliran dengan file disebut membuka file , sementara memutus tautan
ini dinamai menutup file .
Oleh karena itu, kesimpulannya adalah bahwa operasi pertama yang dilakukan di sungai
selalu opendan yang terakhir adalah close. Program ini, pada dasarnya, bebas untuk
memanipulasi aliran antara dua peristiwa ini dan untuk menangani file yang terkait.
Kebebasan ini tentu saja dibatasi oleh karakteristik fisik file dan cara file dibuka.
Mari kita katakan lagi bahwa pembukaan aliran bisa gagal, dan itu bisa terjadi karena beberapa
alasan: yang paling umum adalah kurangnya file dengan nama yang ditentukan.
Bisa juga terjadi bahwa file fisik ada, tetapi program tidak diizinkan untuk membukanya. Ada juga
risiko bahwa program telah membuka terlalu banyak aliran, dan sistem operasi spesifik mungkin
tidak memungkinkan pembukaan simultan lebih dari n file (misalnya, 200).
Program yang ditulis dengan baik harus mendeteksi celah yang gagal ini, dan bereaksi sesuai itu.
Jika pembukaan berhasil, program akan diizinkan untuk melakukan hanya operasi yang
konsisten dengan mode terbuka yang dinyatakan .
Bagaimanapun, file yang berbeda mungkin memerlukan set operasi yang berbeda, dan
berperilaku dengan cara yang berbeda.
Objek kelas yang memadai dibuat ketika Anda membuka file dan memusnahkannya pada saat
penutupan .
Di antara dua peristiwa ini, Anda bisa menggunakan objek untuk menentukan operasi apa yang
harus dilakukan pada aliran tertentu. Operasi yang Anda izinkan digunakan ditentukan oleh cara
Anda membuka file .
Secara umum, objek berasal dari salah satu kelas yang ditampilkan di sini:
Catatan: Anda tidak pernah menggunakan konstruktor untuk menghidupkan objek ini. Satu-
satunya cara Anda mendapatkannya adalah dengan memanggil fungsi yang bernama open() .
Fungsi ini menganalisis argumen yang Anda berikan, dan secara otomatis membuat objek yang
diperlukan.
Jika Anda ingin menyingkirkan objek, Anda memanggil metode bernama close() .
Doa akan memutuskan koneksi ke objek, dan file dan akan menghapus objek.
Untuk tujuan kami, kami hanya akan memperhatikan aliran yang diwakili
oleh BufferIOBasedan TextIOBaseobjek. Anda akan segera mengerti mengapa.
File ini ditulis (atau dibaca) sebagian besar karakter demi karakter, atau baris demi baris.
Aliran biner tidak mengandung teks tetapi urutan byte dari nilai apa pun. Urutan ini dapat,
misalnya, program yang dapat dieksekusi, gambar, audio atau klip video, file database, dll.
Karena file-file ini tidak mengandung garis, membaca dan menulis berhubungan dengan
bagian-bagian data dari berbagai ukuran. Oleh karena itu data dibaca / ditulis byte demi byte,
atau blok demi blok, di mana ukuran blok biasanya berkisar dari satu ke nilai yang dipilih secara
sewenang-wenang.
Kemudian muncul masalah halus. Dalam sistem Unix / Linux, ujung baris ditandai oleh satu
karakter bernama LF(ASCII kode 10) yang ditunjuk dalam program Python sebagai \n.
Sistem operasi lain, terutama yang berasal dari sistem CP / M prasejarah (yang berlaku untuk
sistem keluarga Windows, juga) menggunakan konvensi yang berbeda: akhir baris ditandai oleh
sepasang karakter, CRdan LF(kode ASCII 13 dan 10) yang dapat dikodekan sebagai \r\n.
Fitur-fitur yang tidak diinginkan dari program tersebut, yang mencegah atau menghalangi
penggunaan program dalam lingkungan yang berbeda, disebut tidak mudah dibawa .
Demikian pula, sifat dari program yang memungkinkan eksekusi di lingkungan yang berbeda
disebut portabilitas . Suatu program yang diberkahi dengan sifat seperti itu disebut program
portabel .
Itu dilakukan di tingkat kelas, yang bertanggung jawab untuk membaca dan menulis karakter ke
dan dari aliran. Ini bekerja dengan cara berikut:
ketika aliran terbuka dan disarankan bahwa data dalam file terkait akan diproses sebagai
teks (atau sama sekali tidak ada penasehat seperti itu), maka dialihkan ke mode teks ;
selama membaca / menulis baris dari / ke file terkait, tidak ada yang istimewa terjadi di
lingkungan Unix, tetapi ketika operasi yang sama dilakukan di lingkungan Windows, proses
yang disebut terjemahan karakter baris baru terjadi: ketika Anda membaca baris dari file,
setiap pasangan \r\nkarakter diganti dengan satu \nkarakter, dan sebaliknya; selama
operasi penulisan, setiap \nkarakter diganti dengan sepasang \r\nkarakter;
mekanisme ini sepenuhnya transparan untuk program, yang dapat ditulis seolah-olah itu
dimaksudkan untuk memproses file teks Unix / Linux saja; kode sumber yang dijalankan di
lingkungan Windows juga akan berfungsi dengan baik;
ketika aliran terbuka dan disarankan untuk melakukannya, isinya diambil apa
adanya, tanpa konversi apa pun - tidak ada byte yang ditambahkan atau dihilangkan.
Membuka sungai
The pembukaan sungai dilakukan oleh fungsi yang dapat dipanggil dengan cara berikut:
stream = open(file, mode = 'r', encoding = None)
Akhirnya, pembukaan file yang berhasil akan mengatur posisi file saat ini (kepala baca / tulis
virtual) sebelum byte pertama file jika mode tidaka dan setelah byte file terakhir jika mode diatur
kea .
wt wb menulis
at ab menambahkan
r+t r+b baca dan perbarui
w+t w+b tulis dan perbarui
TAMBAHAN
Anda juga dapat membuka file untuk pembuatan eksklusifnya. Anda dapat melakukan ini
menggunakan xmode terbuka. Jika file sudah ada, open()fungsi akan memunculkan eksepsi.
sys.stdout
o stdout (as standard output)
o the stdout stream is normally associated with the screen, pre-open for writing,
regarded as the primary target for outputting data by the running program;
o the well-known print() function outputs the data to the stdout stream.
sys.stderr
o stderr (as standard error output)
o the stderr stream is normally associated with the screen, pre-open for writing,
regarded as the primary place where the running program should send
information on the errors encountered during its work;
o we haven't presented any method to send the data to this stream (we will do it
soon, we promise)
o pemisahan stdout(hasil berguna yang dihasilkan oleh program) dari stderr(pesan
kesalahan, tidak dapat disangkal bermanfaat tetapi tidak memberikan hasil)
memberikan kemungkinan untuk mengarahkan kedua jenis informasi ini ke target
yang berbeda. Diskusi yang lebih luas tentang masalah ini berada di luar cakupan
kursus kami. Buku pegangan sistem operasi akan memberikan lebih banyak
informasi tentang masalah-masalah ini.
Tindakan yang dilakukan dengan metode dipanggil dari dalam terbuka sungai
objek: stream.close().
nama fungsi pasti komentar sendiri ( close())
fungsi ini tidak mengharapkan argumen; aliran tidak perlu dibuka
fungsi tidak menghasilkan apa-apa selain menimbulkan pengecualian IOError jika terjadi
kesalahan;
sebagian besar pengembang percaya bahwa close()fungsi selalu berhasil dan dengan
demikian tidak perlu memeriksa apakah itu dilakukan dengan benar.
Keyakinan ini hanya dibenarkan sebagian. Jika aliran dibuka untuk penulisan dan
kemudian serangkaian operasi penulisan dilakukan, mungkin saja data yang dikirim ke
aliran belum ditransfer ke perangkat fisik (karena mekanisme yang
disebut caching atau buffering ). Karena penutupan sungai memaksa buffer untuk
menyiramnya, mungkin flushes gagal dan karenanya close()gagal juga.
Kami telah menyebutkan kegagalan yang disebabkan oleh fungsi yang beroperasi dengan
stream tetapi tidak menyebutkan sepatah kata pun bagaimana tepatnya kami dapat
mengidentifikasi penyebab kegagalan tersebut.
Kemungkinan membuat diagnosis ada dan disediakan oleh salah satu komponen pengecualian
stream yang akan kami sampaikan kepada Anda baru saja.
Mendiagnosis masalah aliran
The IOErrorobjek dilengkapi dengan properti bernama errno(nama berasal dari frase nomor
kesalahan ) dan Anda dapat mengaksesnya sebagai berikut:
try: # some stream operations except IOError as exc: print(exc.errno)
Nilai errnoatribut dapat dibandingkan dengan salah satu konstanta simbolis yang telah
ditentukan yang didefinisikan dalam errnomodul.
Mari kita lihat beberapa konstanta terpilih yang berguna untuk mendeteksi kesalahan aliran :
errno.EACCES→ Izin ditolak
Kesalahan terjadi ketika Anda mencoba, misalnya, untuk membuka file dengan atribut hanya
baca untuk menulis.
Daftar lengkapnya jauh lebih lama (termasuk juga beberapa kode kesalahan yang tidak terkait
dengan pemrosesan aliran.)
Untungnya, ada fungsi yang secara dramatis dapat menyederhanakan kode penanganan
kesalahan . Namanya strerror(), dan itu berasal dari osmodul dan mengharapkan hanya satu
argumen - nomor kesalahan .
Perannya sederhana: Anda memberikan nomor kesalahan dan mendapatkan string yang
menjelaskan arti dari kesalahan tersebut.
Catatan: jika Anda melewatkan kode kesalahan yang tidak ada (angka yang tidak terikat
dengan kesalahan aktual), fungsi tersebut akan meningkatkan pengecualian ValueError .
Sekarang kita dapat menyederhanakan kode kita dengan cara berikut:
from os import strerror try: s = open("c:/users/user/Desktop/file.txt", "rt") # actual processing goes
here s.close() except Exception as exc: print("The file could not be opened:", strerror(exc.errno));
Baik. Sekarang saatnya berurusan dengan file teks dan mengenal beberapa teknik dasar yang
dapat Anda gunakan untuk memprosesnya.
Kami akan menunjukkan kepada Anda beberapa teknik dasar yang dapat Anda manfaatkan
untuk membaca konten fileuntuk memprosesnya.
Pemrosesannya akan sangat sederhana - Anda akan menyalin konten file ke konsol, dan
menghitung semua karakter yang telah dibaca oleh program.
Tapi ingat - pemahaman kita tentang file teks sangat ketat. Dalam pengertian kami, ini adalah
file teks biasa - mungkin hanya berisi teks, tanpa dekorasi tambahan (pemformatan, font yang
berbeda, dll.).
Itu sebabnya Anda harus menghindari membuat file menggunakan prosesor teks canggih seperti
MS Word, LibreOffice Writer, atau sesuatu seperti ini. Gunakan dasar-dasar yang ditawarkan OS
Anda: Notepad, vim, gedit, dll.
Jika file teks Anda mengandung beberapa karakter nasional yang tidak tercakup oleh rangkaian
karakter ASCII standar, Anda mungkin perlu langkah tambahan. open()Doa
fungsi Anda mungkin memerlukan argumen yang menunjukkan pengkodean teks tertentu.
Misalnya, jika Anda menggunakan OS Unix / Linux yang dikonfigurasikan untuk menggunakan
UTF-8 sebagai pengaturan seluruh sistem, open()fungsi tersebut mungkin terlihat sebagai berikut:
stream = open('file.txt', 'rt', encoding='utf-8')
di mana argumen pengkodean harus diatur ke nilai yang merupakan string yang mewakili
pengkodean teks yang tepat (UTF-8, di sini).
Baca dokumentasi OS Anda untuk menemukan nama penyandian yang memadai untuk
lingkungan Anda.
INFORMASI
Untuk keperluan percobaan kami dengan pemrosesan file yang dilakukan di bagian ini, kami
akan menggunakan sekumpulan file yang diunggah (mis., File tzop.txt, atau text.txt ) yang dapat
Anda gunakan untuk bekerja . Jika Anda ingin bekerja dengan file Anda sendiri secara lokal di
mesin Anda, kami sangat menganjurkan Anda untuk melakukannya, dan menggunakan IDLE
untuk melakukan tes Anda sendiri.
Kami akan mulai dengan varian paling sederhana dan menggunakan file bernama text.txt. File
memiliki konten berikut:
Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.
Complex is better than complicated.
Ingat - membaca file terabyte-panjang menggunakan metode ini dapat merusak OS Anda .
Jangan berharap keajaiban - memori komputer tidak bisa ditarik.
Metode ini mencoba membaca baris teks lengkap dari file , dan mengembalikannya sebagai
string jika berhasil. Jika tidak, ia mengembalikan string kosong.
Ini membuka peluang baru - sekarang Anda juga dapat menghitung garis dengan mudah, tidak
hanya karakter.
Mari kita manfaatkan itu. Lihatlah kode di editor.
Seperti yang dapat Anda lihat, ide umumnya sama persis seperti pada kedua contoh
sebelumnya.
The readlines()metode, ketika dijalankan tanpa argumen, mencoba untuk membaca semua isi
file, dan mengembalikan daftar string, satu elemen per baris file yang .
Jika Anda tidak yakin apakah ukuran file cukup kecil dan tidak ingin menguji OS, Anda dapat
meyakinkan readlines()metode untuk membaca tidak lebih dari jumlah byte yang ditentukan
sekaligus (nilai pengembalian tetap sama - itu adalah daftar string).
Silakan bereksperimen dengan kode contoh ini untuk memahami cara kerja readlines()metode
ini.
Ukuran buffer input maksimum yang diterima dilewatkan ke metode sebagai argumennya .
Anda mungkin berharap bahwa readlines()dapat memproses konten file lebih efektif
daripada readline(), karena mungkin perlu dipanggil lebih sedikit.
Catatan: ketika tidak ada yang dapat dibaca dari file, metode mengembalikan daftar
kosong. Gunakan itu untuk mendeteksi akhir file.
Sejauh ukuran buffer, Anda dapat berharap bahwa meningkatkannya dapat meningkatkan
kinerja input, tetapi tidak ada aturan emas untuk itu - cobalah untuk menemukan sendiri nilai
optimalnya.
Lihatlah kode di editor. Kami telah memodifikasinya untuk menunjukkan kepada Anda cara
menggunakan readlines().
Kami telah memutuskan untuk menggunakan buffer sepanjang 15 byte. Jangan pikir itu
rekomendasi.
Kami telah menggunakan nilai seperti itu untuk menghindari situasi di
mana readlines()doa pertama menghabiskan seluruh file.
Kami ingin metode ini dipaksa untuk bekerja lebih keras, dan menunjukkan kemampuannya.
Ada dua loop bersarang dalam kode : yang luar menggunakan readlines()hasil untuk beralih
melalui itu, sedangkan yang dalam mencetak karakter garis dengan karakter.
Kami pikir itu mungkin mengejutkan Anda - objek adalah turunan dari kelas iterable .
Aneh? Tidak semuanya. Dapat digunakan? Ya, tentu saja.
The protokol iterasi yang ditetapkan untuk file objek sangat sederhana - nya __next__metode
hanya mengembalikan baris berikutnya dibaca dari file .
Selain itu, Anda dapat berharap bahwa objek secara otomatis memanggil close()ketika salah
satu file yang dibaca mencapai akhir file.
Lihatlah editor dan lihat betapa sederhananya dan jelas kodenya sekarang.
p> Metode ini dinamai write()dan mengharapkan hanya satu argumen - string yang akan
ditransfer ke file terbuka (jangan lupa - mode terbuka harus mencerminkan cara transfer data
- menulis file dibuka dalam mode baca tidak akan berhasil ).
Tidak ada karakter baris baru ditambahkan ke write()argumen 's, jadi Anda harus
menambahkannya sendiri jika Anda ingin file diisi dengan sejumlah baris.
Contoh di editor menunjukkan kode yang sangat sederhana yang membuat file
bernama newtext.txt (catatan: mode terbuka wmemastikan bahwa file akan dibuat dari awal ,
bahkan jika ada dan berisi data) dan kemudian menempatkan sepuluh baris ke dalamnya.
String yang akan direkam terdiri dari baris kata, diikuti oleh nomor baris. Kami telah memutuskan
untuk menulis karakter isi string dengan karakter (ini dilakukan oleh forlingkaran dalam ) tetapi
Anda tidak wajib melakukannya dengan cara ini.
Kami hanya ingin menunjukkan kepada Anda yang write()dapat beroperasi pada satu karakter.
Kode menciptakan file yang diisi dengan teks berikut:
line #1 line #2 line #3 line #4 line #5 line #6 line #7 line #8 line #9 line #10
Kami mendorong Anda untuk menguji perilaku write()metode ini secara lokal di mesin Anda.
Catatan: Anda dapat menggunakan metode yang sama untuk menulis ke stderraliran, tetapi
jangan mencoba membukanya, karena selalu terbuka secara implisit.
Misalnya, jika Anda ingin mengirim string pesan stderruntuk membedakannya dari output
program normal, itu mungkin terlihat seperti ini:
import sys sys.stderr.write("Error message")
Data amorf adalah data yang tidak memiliki bentuk atau bentuk spesifik - mereka hanya
serangkaian byte.
Ini tidak berarti bahwa byte ini tidak dapat memiliki artinya sendiri, atau tidak dapat mewakili
objek yang berguna, misalnya, grafik bitmap.
Aspek yang paling penting dari ini adalah bahwa di tempat di mana kami memiliki kontak
dengan data, kami tidak dapat, atau tidak ingin, tahu apa-apa tentang itu.
Data amorf tidak dapat disimpan menggunakan cara apa pun yang disajikan sebelumnya -
mereka bukan string atau daftar.
Seharusnya ada wadah khusus yang bisa menangani data tersebut.
Python memiliki lebih dari satu wadah seperti itu - salah satunya adalah nama kelas khusus
bytearray - seperti namanya, ini adalah array yang berisi byte (amorf) .
Jika Anda ingin memiliki wadah seperti itu, misalnya, untuk membaca gambar bitmap dan
memprosesnya dengan cara apa pun, Anda harus membuatnya secara eksplisit, menggunakan
salah satu konstruktor yang tersedia.
Lihatlah:
data = bytearray(100)
Doa semacam itu menciptakan objek bytearray yang mampu menyimpan sepuluh byte.
Catatan: konstruktor seperti itu mengisi seluruh array dengan nol .
Ada satu batasan penting - Anda tidak boleh mengatur elemen array byte apa pun dengan nilai
yang bukan bilangan bulat (melanggar aturan ini akan menyebabkan pengecualian TypeError )
dan Anda tidak diizinkan untuk menetapkan nilai yang tidak berasal dari kisaran 0 hingga 255
inklusif (kecuali jika Anda ingin memprovokasi pengecualian ValueError ).
Anda dapat memperlakukan elemen array byte apa pun sebagai nilai integer - seperti dalam
contoh di editor.
Catatan: kami telah menggunakan dua metode untuk mengulangi byte array, dan
memanfaatkan hex()fungsi tersebut untuk melihat elemen yang dicetak sebagai nilai
heksadesimal.
Sekarang kami akan menunjukkan kepada Anda bagaimana menulis array byte ke file biner -
biner, karena kami tidak ingin menyimpan representasi yang dapat dibaca - kami ingin menulis
salinan konten memori fisik satu-ke-satu, byte demi byte.
Jika nilainya berbeda dari panjang argumen metode, itu mungkin mengumumkan beberapa
kesalahan tulis.
Dalam hal ini, kami belum memanfaatkan hasilnya - ini mungkin tidak sesuai dalam setiap kasus.
Cobalah untuk menjalankan kode dan menganalisis konten file output yang baru dibuat.
Anda akan menggunakannya di langkah berikutnya.
Kelas ini memiliki beberapa kesamaan bytearray, dengan pengecualian satu perbedaan
signifikan - tidak dapat diubah .
Untungnya, tidak ada kendala untuk membuat array byte dengan mengambil nilai awalnya
langsung dari objek byte, seperti di sini:
from os import strerror try: bf = open('file.bin', 'rb') data = bytearray(bf.read()) bf.close() for b in
data: print(hex(b), end=' ') except IOError as e: print("I/O error occurred:", strerr(e.errno))
Hati-hati - jangan gunakan pembacaan seperti ini jika Anda tidak yakin bahwa isi file akan sesuai
dengan memori yang tersedia .
Metode ini mencoba membaca jumlah byte yang diinginkan dari file, dan panjang objek yang
dikembalikan dapat digunakan untuk menentukan jumlah byte yang benar-benar dibaca.
Anda dapat menggunakan metode ini seperti di sini:
try: bf = open('file.bin', 'rb') data = bytearray(bf.read(5)) bf.close() for b in data: print(hex(b),
end=' ') except IOError as e: print("I/O error occurred:", strerr(e.errno))
Catatan: lima byte pertama file telah dibaca oleh kode - lima berikutnya masih menunggu untuk
diproses.
Perkiraan waktu
30 menit
Tingkat kesulitan
Medium
Tujuan
meningkatkan keterampilan siswa dalam mengoperasikan file (membaca)
menggunakan koleksi data untuk menghitung banyak data.
Skenario
File teks berisi beberapa teks (tidak ada yang tidak biasa) tetapi kita perlu tahu seberapa sering
(atau seberapa jarang) setiap huruf muncul dalam teks. Analisis semacam itu mungkin berguna
dalam kriptografi, jadi kami ingin dapat melakukannya dengan mengacu pada alfabet Latin.
Tugas Anda adalah menulis sebuah program yang:
meminta pengguna untuk nama file input;
membaca file (jika mungkin) dan menghitung semua huruf Latin (huruf kecil dan huruf
besar diperlakukan sama)
mencetak histogram sederhana dalam urutan abjad (hanya hitungan non-nol yang
disajikan)
Buat file uji untuk kode tersebut, dan periksa apakah histogram Anda berisi hasil yang valid.
Dengan asumsi bahwa file uji hanya berisi satu baris diisi dengan:
aBc
output yang diharapkan akan terlihat sebagai berikut:a -> 1 b -> 1 c -> 1
Kiat :
Kami berpikir bahwa kamus adalah media pengumpulan data yang sempurna untuk
menyimpan jumlah. Huruf-hurufnya mungkin merupakan kunci sementara penghitung bisa
berupa nilai.
6.1.9.16 LAB: Sorted character frequency histogram Lab
LABORATORIUM
Perkiraan waktu
15-20 menit
Tingkat kesulitan
Medium
Prasyarat
05_9.15.1
Tujuan
meningkatkan keterampilan siswa dalam mengoperasikan file (membaca / menulis)
menggunakan lambdas untuk mengubah urutan.
Skenario
Kode sebelumnya perlu ditingkatkan. Tidak apa-apa, tapi itu harus lebih baik.
Tugas Anda adalah membuat beberapa amandemen, yang menghasilkan hasil berikut:
histogram output akan diurutkan berdasarkan frekuensi karakter (penghitung yang lebih
besar harus disajikan terlebih dahulu)
histogram harus dikirim ke file dengan nama yang sama dengan input, tetapi dengan
akhiran '.hist' (harus digabung dengan nama asli)
Dengan asumsi bahwa file input hanya berisi satu baris diisi dengan:
cBabAa
output yang diharapkan akan terlihat sebagai berikut:a -> 3 b -> 2 c -> 1
Kiat :
Gunakan a lambdauntuk mengubah urutan pengurutan.
Perkiraan waktu
30 menit
Tingkat kesulitan
Medium
Tujuan
meningkatkan keterampilan siswa dalam mengoperasikan file (membaca)
menyempurnakan kemampuan siswa dalam mendefinisikan dan menggunakan
pengecualian dan kamus yang ditentukan sendiri.
Skenario
Prof. Jekyll mengadakan kelas bersama siswa dan secara teratur membuat catatan dalam file
teks. Setiap baris file berisi 3 elemen: nama depan siswa, nama belakang siswa, dan jumlah poin
yang diterima siswa selama kelas-kelas tertentu.
Elemen-elemen dipisahkan dengan ruang putih. Setiap siswa dapat muncul lebih dari satu kali di
dalam file Prof. Jekyll.
File tersebut mungkin terlihat sebagai berikut:
John Smith 5 Anna Boleyn 4.5 John Smith 2 Anna Boleyn 11 Andrew Cox 1.5
catatan:
program Anda harus sepenuhnya dilindungi dari semua kemungkinan kegagalan: file
tidak ada, kekosongan file, atau kegagalan data input apa pun; menghadapi kesalahan
data apa pun harus menyebabkan penghentian program segera, dan kesalahan harus
disajikan kepada pengguna;
menerapkan dan menggunakan hierarki pengecualian Anda sendiri - kami telah
menyajikannya di editor; pengecualian kedua harus dimunculkan ketika baris yang buruk
terdeteksi, dan yang ketiga ketika file sumber ada tetapi kosong.
Kiat :
Gunakan kamus untuk menyimpan data siswa.