Dari penerjemahBrandon Rhodes adalah orang yang sangat sederhana yang menampilkan dirinya di Twitter sebagai "seorang programmer Python yang membayar pinjaman kepada komunitas dalam bentuk laporan atau esai." Jumlah "laporan dan esai" ini mengesankan, seperti juga jumlah proyek gratis yang menjadi kontributor Brandon. Dan Brandon telah menerbitkan dua buku dan sedang menulis buku ketiga.
Saya sering menemukan dalam komentar tentang Habré kesalahpahaman mendasar atau penolakan terhadap bahasa dinamis, pengetikan dinamis, pemrograman umum dan paradigma lainnya. Saya menerbitkan terjemahan (transkrip) resmi dari salah satu laporan Brandon ini dengan harapan akan membantu programmer yang ada dalam paradigma bahasa statis untuk lebih memahami bahasa dinamis, khususnya Python.
Seperti biasa dengan saya, saya meminta Anda untuk memberi tahu saya di PM tentang kesalahan dan kesalahan ketik saya.
Apa arti frasa "kasus marginal" dalam judul laporan saya? Kasing pembatas muncul ketika Anda mengulangi urutan opsi hingga Anda mencapai nilai ekstrem. Misalnya, poligon bersisi-n. Jika n = 3, maka ini adalah segitiga, n = 4 adalah segi empat, n = 5 adalah segi lima, dll. Saat n mendekati tak terhingga, sisi-sisinya menjadi lebih kecil dan lebih besar, dan garis besar poligon menjadi seperti lingkaran. Jadi, lingkaran adalah pembatas untuk poligon reguler. Inilah yang terjadi ketika ide tertentu dibawa ke batas.
Saya ingin berbicara tentang Python sebagai kasus ekstrim untuk C ++. Jika Anda mengambil semua ide bagus dari C ++ dan membersihkannya hingga kesimpulan logisnya, saya yakin Anda akan berakhir dengan Python secara alami ketika serangkaian poligon muncul ke lingkaran.
Aset non-inti
Saya menjadi tertarik pada Python di tahun 90-an: itu adalah periode dalam hidup saya ketika saya menyingkirkan "aset non-inti", demikian saya menyebutnya. Banyak hal mulai membuatku bosan. Gangguan, misalnya. Ingat, dulu di banyak papan komputer ada kontak dengan jumper? Dan Anda mengatur jumper ini pada manual sehingga kartu video menerima interupsi prioritas yang lebih tinggi, sehingga game Anda berjalan lebih cepat? Jadi, saya bosan mengalokasikan dan membebaskan memori menggunakan malloc()
dan free()
sekitar waktu yang sama ketika saya berhenti menyesuaikan kinerja komputer saya dengan jumper. Itu sekitar tahun 1997.
Maksudku, ketika kita mempelajari suatu proses, kita biasanya berusaha untuk mendapatkan kontrol penuh atas itu, untuk memiliki semua tuas dan tombol yang mungkin. Kemudian beberapa orang masih terpesona dengan kemungkinan kontrol ini. Tetapi karakter saya adalah bahwa begitu saya terbiasa dengan manajemen dan mengerti apa itu, saya segera mulai mencari kesempatan untuk mengundurkan diri dari kekuatan saya, mentransfer tuas dan tombol ke beberapa mesin sehingga memberikan gangguan bagi saya.
Oleh karena itu, di akhir tahun 90-an, saya mencari bahasa pemrograman yang memungkinkan saya untuk fokus pada area subjek dan pemodelan tugas, daripada mengkhawatirkan area memori komputer saya yang disimpan. Bagaimana kita bisa menyederhanakan C ++ tanpa mengulangi dosa bahasa scripting terkenal?
Misalnya, saya tidak bisa menggunakan Perl, dan Anda tahu mengapa? Tanda dolar ini! Dia segera menjelaskan bahwa pencipta Perl tidak mengerti bagaimana bahasa pemrograman bekerja. Anda menggunakan dolar dalam Bash untuk memisahkan nama variabel dari sisa string, karena program Bash terdiri dari perintah yang dirasakan secara harfiah dan parameternya. Tetapi setelah Anda mengetahui bahasa pemrograman ini, di mana string ditempatkan di antara pasangan karakter kecil yang disebut tanda kutip, dan tidak di seluruh teks program, Anda mulai menganggap $
sebagai sampah visual. Tanda dolar itu tidak berguna, itu jelek, itu harus pergi! Jika Anda ingin merancang bahasa untuk pemrograman serius, Anda tidak boleh menggunakan karakter khusus untuk menunjukkan variabel.
Sintaks
Bagaimana dengan sintaks? Ambil C sebagai basis! Ini bekerja dengan cukup baik. Biarkan penugasan dilambangkan dengan tanda sama dengan. Penunjukan ini tidak diterima dalam semua bahasa, tetapi, dengan satu atau lain cara, banyak yang terbiasa dengannya. Tapi jangan membuat tugas ekspresi. Pengguna bahasa kami tidak hanya akan programmer profesional, tetapi juga anak sekolah, ilmuwan, atau ilmuwan data (jika Anda tidak mengetahui kategori pengguna mana yang menulis kode terburuk, maka saya akan mengisyaratkan bahwa ini bukan anak sekolah). Kami tidak akan memberi pengguna kesempatan untuk mengubah keadaan variabel di tempat yang tidak terduga, dan kami akan menjadikan penugasan sebagai operator.
Lalu apa yang harus digunakan untuk menunjukkan kesetaraan jika tanda yang sama telah digunakan untuk penugasan? Tentu saja, tugas ganda, seperti yang dilakukan di C! Banyak yang sudah terbiasa. Kami juga akan meminjam dari C notasi untuk semua operasi aritmatika dan bitwise, karena notasi ini bekerja, dan banyak yang cukup senang dengannya.
Tentu saja, kita dapat meningkatkan sesuatu. Apa yang Anda pikirkan ketika Anda melihat tanda persen dalam teks program? Tentang interpolasi string, tentu saja! Meskipun %
terutama merupakan operator penangkap modul, itu hanya tidak terdefinisi untuk string. Dan jika demikian, mengapa tidak menggunakannya kembali?
Numerik dan string literal yang mengontrol urutan dengan garis miring terbalik - semua ini akan terlihat seperti di C.
Kontrol aliran eksekusi? Sama if
, yang else
, while
, break
dan continue
. Tentu saja, kami akan menambahkan sedikit kesenangan dengan mengkooptasi yang lama yang baik for
beralih pada struktur data dan rentang nilai. Ini akan diusulkan nanti dalam C ++ 11, tetapi dalam Python, operator for
awalnya merangkum semua operasi untuk menghitung ukuran, melintasi tautan, menambah penghitung, dll., Dengan kata lain, melakukan segala sesuatu yang diperlukan untuk menyediakan elemen struktur data kepada pengguna. Jenis struktur apa? Tidak masalah, cukup berikan saja, itu akan dipecahkan.
Kami juga akan meminjam pengecualian dari C ++, tetapi kami akan membuatnya sangat murah dalam hal konsumsi sumber daya sehingga dapat digunakan tidak hanya untuk menangani kesalahan, tetapi juga untuk mengontrol aliran eksekusi. Kami akan membuat pengindeksan lebih menarik dengan menambahkan slicing - kemampuan untuk mengindeks tidak hanya elemen individu dari struktur data sekuensial, tetapi juga rentangnya.
Oh ya! Kami akan memperbaiki cacat desain asli di C - tambahkan koma menggantung!
Kisah ini dimulai dengan Pascal, bahasa yang mengerikan di mana tanda titik koma digunakan sebagai pembatas ekspresi. Ini berarti bahwa pengguna harus meletakkan titik koma di akhir setiap ekspresi di blok kecuali yang terakhir . Oleh karena itu, setiap kali Anda mengubah urutan ekspresi dalam sebuah program di Pascal, Anda berisiko menerima kesalahan sintaksis jika Anda tidak memastikan untuk menghapus titik koma dari baris terakhir dan menambahkannya ke akhir baris yang dulunya adalah yang terakhir.
If (n = 0) then begin writeln('N is now zero'); func := 1 end
Kernigan dan Ritchie melakukan hal yang benar ketika mereka mendefinisikan titik koma dalam C sebagai terminator ekspresi, bukan pembatas, menciptakan simetri yang indah ketika setiap baris dalam program, termasuk yang terakhir, berakhir sama dan dapat dipertukarkan secara bebas. Sayangnya, di masa depan, rasa harmoni berubah untuk mereka, dan mereka menjadikan koma sebagai pemisah dalam inisialisasi statis. Ini terlihat bagus ketika ekspresi cocok pada satu baris:
int a[] = {4, 5, 6};
tetapi ketika inisialisasi Anda menjadi lebih lama dan Anda mengaturnya secara vertikal, Anda mendapatkan asimetri tidak nyaman yang sama seperti di Pascal:
int a[] = { 4, 5, 6 };
Pada tahap awal pengembangannya, Python membuat koma menggantung dalam struktur data sepenuhnya opsional, terlepas dari bagaimana elemen-elemen struktur ini disusun: horizontal atau vertikal. By the way, ini sangat nyaman untuk pembuatan kode otomatis: Anda tidak perlu memperlakukan elemen terakhir sebagai kasus khusus.
Kemudian, standar C99 dan C ++ 11 juga mengoreksi kesalahpahaman awal, memungkinkan Anda untuk menambahkan koma setelah literal terakhir di penginisialisasi.
Ruang nama
Kita juga perlu mengimplementasikan dalam bahasa pemrograman kita seperti ruang nama atau ruang nama. Ini adalah bagian penting dari bahasa yang seharusnya menyelamatkan kita dari kesalahan seperti konflik nama. Kami akan melakukannya lebih mudah daripada C ++: alih-alih memberikan pengguna kemampuan untuk secara acak menamai namespace, kami akan membuat satu namespace per modul (file) dan menunjuknya dengan nama file. Misalnya, jika Anda membuat modul foo.py
, modul tersebut akan diberi nama namespace foo
.
Untuk bekerja dengan model ruang nama yang disederhanakan, pengguna hanya perlu satu operator.
Buat direktori my_package
, letakkan file my_module.py
, dan deklarasikan kelas dalam file:
class C(object): READ = 1 WRITE = 2
maka akses ke atribut kelas adalah sebagai berikut:
import my_package.my_module my_package.my_module.C.READ
Jangan khawatir, kami tidak akan memaksa pengguna untuk mencetak nama lengkap setiap kali. Kami akan memberinya kesempatan untuk menggunakan beberapa versi pernyataan import
untuk memvariasikan tingkat "kedekatan" ruang nama:
import my_package.my_module my_package.my_module.C.READ from my_package import my_module my_module.C.READ from my_package.my_module import C C.READ
Dengan demikian, nama yang sama yang diberikan dalam paket berbeda tidak akan pernah konflik:
import json j = json.load(file) import pickle p = pickle.load(file)
Fakta bahwa setiap modul memiliki namespace sendiri juga berarti bahwa kita tidak memerlukan pengubah static
. Namun, kami mengingat satu fungsi yang dilakukan static
- merangkum variabel internal. Untuk menunjukkan kepada kolega bahwa nama yang diberikan (variabel, kelas, atau modul) bukan publik, kami memulainya dengan garis bawah, misalnya, _ignore_this
. Ini juga bisa menjadi sinyal bagi IDE untuk tidak menggunakan nama ini dalam penyelesaian otomatis.
Kelebihan fungsi
Kami tidak akan menerapkan fungsi kelebihan muatan dalam bahasa kami. Mekanisme kelebihan beban terlalu rumit. Sebagai gantinya, kami akan menggunakan argumen opsional dengan nilai default yang dapat dihilangkan dari panggilan, serta argumen bernama untuk "melompati" argumen opsional dengan default yang valid dan hanya menetapkan nilai-nilai yang berbeda dari yang default. Yang penting, kurangnya kelebihan akan menyelamatkan kita dari kebutuhan untuk menentukan fungsi mana dari set fungsi kelebihan beban baru saja dipanggil, bagaimana manajer panggilan bekerja: fungsi selalu satu dalam modul ini, mudah untuk menemukan namanya.
API Sistem
Kami akan memberikan pengguna akses penuh ke banyak API sistem, termasuk soket. Saya tidak mengerti mengapa penulis bahasa scripting selalu menawarkan cara cerdik mereka sendiri untuk membuka soket. Namun, mereka tidak pernah mewujudkan API Unix Socket penuh. Mereka menerapkan 5-6 fungsi yang mereka pahami, dan membuang yang lainnya. Python, tidak seperti mereka, memiliki modul standar untuk berinteraksi dengan OS yang mengimplementasikan setiap panggilan sistem standar. Itu berarti Anda dapat membuka buku Stevens sekarang dan mulai menulis kode. Dan semua soket, proses, dan garpu Anda akan bekerja persis seperti yang dikatakan. Ya, mungkin Guido atau kontributor Python awal melakukan hal itu, karena mereka terlalu malas untuk menulis implementasi perpustakaan sistem mereka, terlalu malas untuk menjelaskan kepada pengguna lagi bagaimana soket bekerja. Tetapi sebagai hasilnya, mereka mencapai efek yang luar biasa: Anda dapat mentransfer semua pengetahuan UNIX yang Anda peroleh dalam C dan C ++ ke lingkungan Python.
Jadi, kami memutuskan fitur apa yang akan kami "pinjam" dari C ++ untuk membuat bahasa skrip sederhana kami. Sekarang kita perlu memutuskan apa yang ingin kita perbaiki.
Perilaku tidak terdefinisi
Perilaku tidak dikenal, perilaku tidak terdefinisi, perilaku yang didefinisikan oleh implementasi ... Ini semua adalah ide buruk untuk bahasa yang akan digunakan oleh anak sekolah, ilmuwan, dan ilmuwan data. Dan perolehan kinerja yang memungkinkan hal-hal seperti itu sering diabaikan dibandingkan dengan ketidaknyamanan. Sebagai gantinya, kami akan mengumumkan bahwa setiap program yang benar secara sintaksis menghasilkan hasil yang sama pada platform apa pun. Kami akan menjelaskan standar bahasa dengan frasa seperti "Python mengevaluasi semua ekspresi dari kiri ke kanan" alih-alih mencoba menyusun ulang perhitungan tergantung pada prosesor, OS, atau fase bulan. Jika pengguna yakin bahwa urutan perhitungan itu penting, ia memiliki hak untuk menulis ulang kode dengan benar: pada akhirnya, pengguna adalah yang utama.
Prioritas Operasi
Anda pasti mengalami kesalahan serupa: ekspresi
oflags & 0x80 == nflags & 0x80
selalu mengembalikan 0, karena perbandingan dalam C didahulukan dari operasi bitwise. Dengan kata lain, ungkapan ini dievaluasi menjadi
oflags & (0x80 == nflags) & 0x80
Oh, itu C!
Kami akan menghilangkan kemungkinan penyebab kesalahan tersebut dalam bahasa scripting sederhana kami, menempatkan prioritas operasi perbandingan di belakang aritmatika dan manipulasi bit, sehingga ekspresi dari contoh kami dihitung lebih intuitif:
(oflags & 0x80) == (nflags & 0x80)
Perbaikan lainnya
Keterbacaan kode penting bagi kami. Jika operasi aritmatika bahasa C akrab bagi pengguna bahkan oleh aritmatika sekolah, maka kebingungan antara operasi logis dan bitwise merupakan sumber kesalahan yang jelas. Kami akan mengganti ampersand ganda dengan kata and
, dan garis vertikal ganda dengan kata or
, sehingga bahasa kita lebih mirip ucapan manusia daripada piket karakter "komputer".
Kami akan menyerahkan kemungkinan perhitungan yang disingkat ke operator logis kami ( https://en.wikipedia.org/wiki/Short-circuit_evaluation ), tetapi juga memberi mereka kemampuan untuk mengembalikan nilai akhir dari jenis apa pun, bukan hanya Boolean. Maka ungkapan suka
s = error.message or 'Error'
Dalam contoh ini, variabel akan diatur ke error.message
jika tidak kosong, jika tidak maka string 'Kesalahan'.
Kami memperluas gagasan C bahwa 0 sama dengan false untuk objek selain bilangan bulat. Misalnya, pada garis dan wadah kosong.
Kami akan menghancurkan integer overflow. Bahasa kami akan konsisten dalam implementasi dan mudah digunakan, sehingga pengguna kami tidak perlu mengingat nilai khusus yang mencurigakan mendekati dua miliar, setelah itu keseluruhan, bertambah satu, tiba-tiba berubah tanda. Kami menerapkan bilangan bulat seperti itu yang akan berperilaku seperti bilangan bulat sampai menghabiskan semua memori yang tersedia.
Mengetik dengan ketat vs lemah
Masalah penting lainnya dalam desain bahasa scripting: ketegaran mengetik. Banyak di antara hadirin yang akrab dengan JavaScript? Apa yang terjadi jika angka 3 dikurangi dari string '4'?
js> '4' - 3 1
Hebat! Dan jika Anda menambahkan angka 3 ke string '4'?
js> '4' + 3 "43"
Ini disebut mengetik lemah (atau lemah). Ini adalah sesuatu seperti inferiority complex ketika sebuah bahasa pemrograman berpikir bahwa seorang programmer akan mengutuknya jika ia tidak dapat mengembalikan hasil dari ekspresi apa pun, bahkan jelas tidak berarti, dengan berulang kali mengetikkan tipe. Masalahnya adalah konversi jenis, yang dihasilkan bahasa yang diketik dengan lemah secara otomatis, sangat jarang mengarah pada hasil yang bermakna. Mari kita coba konversi yang sedikit lebih rumit:
js> [] + [] "" js> [] + {} "[object Object]"
Kami berharap bahwa operasi penambahan bersifat komutatif, tetapi apa yang terjadi jika kami mengubah ketentuan dalam kasus yang terakhir?
js> {} + [] 0
JavaScript tidak sendirian dalam masalahnya. Perl dalam situasi yang sama juga mencoba mengembalikan setidaknya sesuatu:
perl> "3" + 1 4
Dan awk akan melakukan sesuatu seperti itu:
$ echo | awk '{print "3" + 1}' 4
Pembuat bahasa scripting secara tradisional percaya bahwa pengetikan yang longgar itu nyaman . Mereka salah: mengetik longgar itu mengerikan ! Itu melanggar prinsip lokalitas. Jika ada kesalahan dalam kode, maka bahasa pemrograman harus memberitahu pengguna tentang hal itu, menyebabkan pengecualian sedekat mungkin ke tempat bermasalah dalam kode. Tetapi dalam semua bahasa ini, yang tanpa henti mengetik jenis, sampai sesuatu beres, kontrol biasanya sampai pada akhirnya, dan kami mendapatkan hasilnya, menilai dimana, dalam program kami, ada sesuatu yang salah di suatu tempat. Dan kita harus men-debug seluruh program kita, satu demi satu baris, untuk menemukan kesalahan ini.
Pengetikan yang longgar juga menurunkan pembacaan kode, karena meskipun kita dengan benar menggunakan casting tipe implisit dalam suatu program, hal ini terjadi secara tak terduga untuk programmer lain.
Dalam Python, seperti dalam C ++, ekspresi seperti itu akan mengembalikan kesalahan.
>>> '4' - 3 TypeError >>> '4' + 3 TypeError
Karena tipe casting, jika benar-benar diperlukan, mudah untuk menulis secara eksplisit:
>>> int('4') + 3 7 >>> '4' + str(3) '43'
Kode ini mudah dibaca dan dipelihara, memperjelas apa yang sebenarnya terjadi dalam program, yang mengarah ke hasil ini. Ini karena programmer Python percaya bahwa eksplisit lebih baik daripada implisit, dan kesalahan tidak boleh tidak diketahui.
Python adalah bahasa yang diketik dengan kuat, dan satu-satunya konversi tipe implisit di dalamnya terjadi selama operasi aritmatika pada bilangan bulat, yang hasilnya harus dinyatakan sebagai angka pecahan. Mungkin ini juga tidak boleh diizinkan dalam program, tetapi dalam kasus ini terlalu banyak pengguna harus segera menjelaskan perbedaan antara bilangan bulat dan angka floating point, yang akan mempersulit langkah pertama mereka dengan Python.
Lanjutan: “ Python sebagai kasus akhir C ++. Bagian 2/2 . "