Mengapa Ada Begitu Banyak Python? Perbandingan Implementasi Python

Diterbitkan: 2022-03-11

Python luar biasa.

Anehnya, itu pernyataan yang cukup ambigu. Apa yang saya maksud dengan 'Python'? Apakah maksud saya Python antarmuka abstrak? Maksud saya CPython, implementasi Python yang umum (dan jangan bingung dengan Cython yang bernama sama)? Atau apakah saya bermaksud sesuatu yang lain sama sekali? Mungkin saya secara tidak langsung mengacu pada Jython, atau IronPython, atau PyPy. Atau mungkin saya benar-benar telah keluar dari ujung yang dalam dan saya sedang berbicara tentang RPython atau RubyPython (yang merupakan hal yang sangat, sangat berbeda).

Sementara teknologi yang disebutkan di atas diberi nama umum dan referensi umum, beberapa di antaranya melayani tujuan yang sama sekali berbeda (atau, setidaknya, beroperasi dengan cara yang sama sekali berbeda).

Sepanjang waktu saya bekerja dengan antarmuka Python, saya telah menemukan banyak alat .*ython ini. Tetapi baru-baru ini saya meluangkan waktu untuk memahami apa itu, bagaimana cara kerjanya, dan mengapa mereka diperlukan (dengan cara mereka sendiri).

Dalam tutorial ini, saya akan mulai dari awal dan menelusuri berbagai implementasi Python, diakhiri dengan pengenalan menyeluruh ke PyPy, yang saya yakini adalah masa depan bahasa tersebut.

Semuanya dimulai dengan pemahaman tentang apa sebenarnya 'Python' itu.

Jika Anda memiliki pemahaman yang baik tentang kode mesin, mesin virtual, dan sejenisnya, silakan langsung saja.

"Apakah Python ditafsirkan atau dikompilasi?"

Ini adalah titik kebingungan umum untuk pemula Python.

Hal pertama yang harus disadari ketika membuat perbandingan adalah bahwa 'Python' adalah sebuah antarmuka . Ada spesifikasi tentang apa yang harus dilakukan Python dan bagaimana perilakunya (seperti halnya antarmuka apa pun). Dan ada beberapa implementasi (seperti halnya antarmuka apa pun).

Hal kedua yang harus disadari adalah bahwa 'ditafsirkan' dan 'dikompilasi' adalah properti dari implementasi , bukan antarmuka .

Jadi pertanyaannya sendiri tidak benar-benar terbentuk dengan baik.

Apakah Python ditafsirkan atau dikompilasi? Pertanyaannya tidak benar-benar terbentuk dengan baik.

Yang mengatakan, untuk implementasi Python yang paling umum (CPython: ditulis dalam C, sering disebut hanya sebagai 'Python', dan tentunya apa yang Anda gunakan jika Anda tidak tahu apa yang saya bicarakan), jawabannya adalah: ditafsirkan , dengan beberapa kompilasi. CPython mengkompilasi * kode sumber Python ke bytecode, dan kemudian menafsirkan bytecode ini, mengeksekusinya saat berjalan.

* Catatan: ini bukan 'kompilasi' dalam pengertian tradisional. Biasanya, kami akan mengatakan bahwa 'kompilasi' menggunakan bahasa tingkat tinggi dan mengubahnya menjadi kode mesin. Tapi itu semacam 'kompilasi'.

Mari kita lihat jawaban itu lebih dekat, karena ini akan membantu kita memahami beberapa konsep yang muncul nanti di postingan.

Bytecode vs. Kode Mesin

Sangat penting untuk memahami perbedaan antara bytecode vs. kode mesin (alias kode asli), mungkin paling baik diilustrasikan dengan contoh:

  • C mengkompilasi ke kode mesin, yang kemudian dijalankan langsung pada prosesor Anda. Setiap instruksi menginstruksikan CPU Anda untuk memindahkan barang-barang.
  • Java mengkompilasi ke bytecode, yang kemudian dijalankan di Java Virtual Machine (JVM), sebuah abstraksi dari komputer yang mengeksekusi program. Setiap instruksi kemudian ditangani oleh JVM, yang berinteraksi dengan komputer Anda.

Dalam istilah yang sangat singkat: kode mesin jauh lebih cepat, tetapi bytecode lebih portabel dan aman .

Kode mesin terlihat berbeda tergantung pada mesin Anda, tetapi bytecode terlihat sama di semua mesin. Orang mungkin mengatakan bahwa kode mesin dioptimalkan untuk pengaturan Anda.

Kembali ke implementasi CPython, proses toolchain adalah sebagai berikut:

  1. CPython mengkompilasi kode sumber Python Anda menjadi bytecode.
  2. Bytecode itu kemudian dieksekusi di CPython Virtual Machine.
Pemula sering menganggap Python dikompilasi karena file .pyc. Ada beberapa kebenarannya: file .pyc adalah bytecode yang dikompilasi, yang kemudian ditafsirkan. Jadi, jika Anda telah menjalankan kode Python sebelumnya dan memiliki file .pyc, itu akan berjalan lebih cepat untuk kedua kalinya, karena tidak perlu mengkompilasi ulang bytecode.

VM Alternatif: Jython, IronPython, dan Lainnya

Seperti yang saya sebutkan sebelumnya, Python memiliki beberapa implementasi. Sekali lagi, seperti yang disebutkan sebelumnya, yang paling umum adalah CPython, tetapi ada yang lain yang harus disebutkan demi panduan perbandingan ini. Ini adalah implementasi Python yang ditulis dalam C dan dianggap sebagai implementasi 'default'.

Tapi bagaimana dengan implementasi Python alternatif? Salah satu yang lebih menonjol adalah Jython, implementasi Python yang ditulis Java yang memanfaatkan JVM. Sementara CPython menghasilkan bytecode untuk dijalankan pada VM CPython, Jython menghasilkan bytecode Java untuk dijalankan pada JVM (ini adalah hal yang sama yang dihasilkan ketika Anda mengkompilasi program Java).

Penggunaan bytecode Java oleh Jython digambarkan dalam diagram implementasi Python ini.

“Mengapa Anda pernah menggunakan implementasi alternatif?”, Anda mungkin bertanya. Nah, untuk satu, implementasi Python yang berbeda ini cocok dengan tumpukan teknologi yang berbeda .

CPython membuatnya sangat mudah untuk menulis ekstensi C untuk kode Python Anda karena pada akhirnya dieksekusi oleh juru bahasa C. Jython, di sisi lain, membuatnya sangat mudah untuk bekerja dengan program Java lainnya: Anda dapat mengimpor kelas Java apa pun tanpa usaha tambahan, memanggil dan memanfaatkan kelas Java Anda dari dalam program Jython Anda. (Selain: jika Anda belum memikirkannya dengan cermat, ini sebenarnya gila. Kami berada pada titik di mana Anda dapat mencampur dan mencampur bahasa yang berbeda dan mengompilasi semuanya ke dalam substansi yang sama. (Seperti yang disebutkan oleh Rostin, program yang campur kode Fortran dan C sudah ada sejak lama. Jadi, tentu saja, ini belum tentu baru. Tapi tetap keren.))

Sebagai contoh, ini adalah kode Jython yang valid:

 [Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]

IronPython adalah implementasi Python populer lainnya, yang seluruhnya ditulis dalam C# dan menargetkan tumpukan .NET. Secara khusus, ini berjalan pada apa yang Anda sebut .NET Virtual Machine, Microsoft Common Language Runtime (CLR), sebanding dengan JVM.

Anda mungkin mengatakan bahwa Jython : Java :: IronPython : C# . Mereka berjalan pada masing-masing VM yang sama, Anda dapat mengimpor kelas C# dari kode IronPython Anda dan kelas Java dari kode Jython Anda, dll.

Sangat mungkin untuk bertahan tanpa pernah menyentuh implementasi Python non-CPython. Tetapi ada keuntungan yang bisa didapat dari peralihan, yang sebagian besar bergantung pada tumpukan teknologi Anda. Menggunakan banyak bahasa berbasis JVM? Jython mungkin cocok untuk Anda. Semua tentang tumpukan .NET? Mungkin Anda harus mencoba IronPython (dan mungkin sudah pernah).

Bagan perbandingan Python ini menunjukkan perbedaan antara implementasi Python.

Omong-omong: meskipun ini bukan alasan untuk menggunakan implementasi yang berbeda, perhatikan bahwa implementasi ini sebenarnya berbeda dalam perilaku di luar cara mereka memperlakukan kode sumber Python Anda. Namun, perbedaan ini biasanya kecil, dan menghilang atau muncul seiring waktu karena implementasi ini sedang dalam pengembangan aktif. Misalnya, IronPython menggunakan string Unicode secara default; CPython, bagaimanapun, default ke ASCII untuk versi 2.x (gagal dengan UnicodeEncodeError untuk karakter non-ASCII), tetapi mendukung string Unicode secara default untuk 3.x.

Kompilasi Tepat Waktu: PyPy, dan Masa Depan

Jadi kami memiliki implementasi Python yang ditulis dalam C, satu di Java, dan satu di C#. Langkah logis berikutnya: implementasi Python yang ditulis dalam… Python. (Pembaca yang berpendidikan akan mencatat bahwa ini sedikit menyesatkan.)

Di sinilah hal-hal yang mungkin membingungkan. Pertama, mari kita bahas kompilasi just-in-time (JIT).

JIT: Mengapa dan Bagaimana

Ingat bahwa kode mesin asli jauh lebih cepat daripada bytecode. Nah, bagaimana jika kita bisa mengkompilasi beberapa bytecode kita dan kemudian menjalankannya sebagai kode asli? Kita harus membayar beberapa harga untuk mengkompilasi bytecode (yaitu, waktu), tetapi jika hasil akhirnya lebih cepat, itu bagus! Ini adalah motivasi kompilasi JIT, teknik hibrida yang menggabungkan manfaat juru bahasa dan kompiler. Dalam istilah dasar, JIT ingin memanfaatkan kompilasi untuk mempercepat sistem yang ditafsirkan.

Misalnya, pendekatan umum yang diambil oleh JIT:

  1. Identifikasi bytecode yang sering dieksekusi.
  2. Kompilasi ke kode mesin asli.
  3. Cache hasilnya.
  4. Setiap kali bytecode yang sama diatur untuk dijalankan, alih-alih ambil kode mesin yang telah dikompilasi sebelumnya dan dapatkan manfaatnya (yaitu, peningkatan kecepatan).

Inilah yang dimaksud dengan implementasi PyPy: membawa JIT ke Python (lihat Lampiran untuk upaya sebelumnya). Tentu saja ada tujuan lain: PyPy bertujuan untuk menjadi lintas platform, ringan memori, dan mendukung tanpa tumpukan. Tapi JIT benar-benar nilai jualnya. Sebagai rata-rata selama banyak tes waktu, dikatakan meningkatkan kinerja dengan faktor 6,27. Untuk rinciannya, lihat bagan ini dari Pusat Kecepatan PyPy:

Membawa JIT ke antarmuka Python menggunakan implementasi PyPy terbayar dalam peningkatan kinerja.

PyPy Sulit Dimengerti

PyPy memiliki potensi besar, dan pada titik ini sangat kompatibel dengan CPython (sehingga dapat menjalankan Flask, Django, dll.).

Tetapi ada banyak kebingungan seputar PyPy (lihat, misalnya, proposal tidak masuk akal ini untuk membuat PyPyPy…). Menurut pendapat saya, itu terutama karena PyPy sebenarnya adalah dua hal:

  1. Penerjemah Python yang ditulis dalam RPython (bukan Python (saya berbohong sebelumnya)). RPython adalah bagian dari Python dengan pengetikan statis. Dalam Python, "kebanyakan tidak mungkin" untuk bernalar secara ketat tentang tipe (Mengapa begitu sulit? Pertimbangkan fakta bahwa:

     x = random.choice([1, "foo"])

    akan menjadi kode Python yang valid (kredit ke Ademan). Apa jenis x ? Bagaimana kita bisa bernalar tentang jenis variabel ketika jenisnya bahkan tidak ditegakkan secara ketat?). Dengan RPython, Anda mengorbankan beberapa fleksibilitas, tetapi malah membuatnya jauh lebih mudah untuk mempertimbangkan manajemen memori dan yang lainnya, yang memungkinkan pengoptimalan.

  2. Kompiler yang mengkompilasi kode RPython untuk berbagai target dan menambahkan dalam JIT. Platform default adalah C, yaitu kompiler RPython-to-C, tetapi Anda juga dapat menargetkan JVM dan lainnya.

Semata-mata untuk kejelasan dalam panduan perbandingan Python ini, saya akan menyebutnya sebagai PyPy (1) dan PyPy (2).

Mengapa Anda membutuhkan dua hal ini, dan mengapa di bawah satu atap? Pikirkan seperti ini: PyPy (1) adalah juru bahasa yang ditulis dalam RPython. Jadi dibutuhkan kode Python pengguna dan mengkompilasinya menjadi bytecode. Tetapi interpreter itu sendiri (ditulis dalam RPython) harus ditafsirkan oleh implementasi Python lain untuk dapat dijalankan, bukan?

Nah, kita bisa saja menggunakan CPython untuk menjalankan interpreter. Tapi itu tidak akan terlalu cepat.

Sebaliknya, idenya adalah bahwa kita menggunakan PyPy (2) (disebut sebagai RPython Toolchain) untuk mengkompilasi penerjemah PyPy ke kode untuk platform lain (misalnya, C, JVM, atau CLI) untuk dijalankan di mesin kita, menambahkan JIT sebagai dengan baik. Ini ajaib: PyPy secara dinamis menambahkan JIT ke juru bahasa, menghasilkan kompilernya sendiri! ( Sekali lagi, ini gila: kami mengkompilasi juru bahasa, menambahkan kompiler terpisah yang berdiri sendiri. )

Pada akhirnya, hasilnya adalah executable mandiri yang menginterpretasikan kode sumber Python dan mengeksploitasi optimasi JIT. Yang hanya apa yang kami inginkan! Ini seteguk, tapi mungkin diagram ini akan membantu:

Diagram ini menggambarkan keindahan implementasi PyPy, termasuk juru bahasa, kompiler, dan yang dapat dieksekusi dengan JIT.

Untuk mengulangi, keindahan sebenarnya dari PyPy adalah bahwa kita dapat menulis sendiri banyak interpreter Python yang berbeda di RPython tanpa khawatir tentang JIT. PyPy kemudian akan mengimplementasikan JIT untuk kita menggunakan RPython Toolchain/PyPy (2).

Bahkan, jika kita menjadi lebih abstrak, Anda secara teoritis dapat menulis penerjemah untuk bahasa apa pun , memasukkannya ke PyPy, dan mendapatkan JIT untuk bahasa itu. Ini karena PyPy berfokus pada pengoptimalan penerjemah yang sebenarnya, daripada detail bahasa yang ditafsirkannya.

Anda secara teoritis dapat menulis juru bahasa untuk bahasa apa pun, memasukkannya ke PyPy, dan mendapatkan JIT untuk bahasa itu.

Sebagai penyimpangan singkat, saya ingin menyebutkan bahwa JIT itu sendiri benar-benar menarik. Ini menggunakan teknik yang disebut penelusuran, yang dijalankan sebagai berikut:

  1. Jalankan penerjemah dan tafsirkan semuanya (tanpa menambahkan JIT).
  2. Lakukan beberapa profil ringan dari kode yang ditafsirkan.
  3. Identifikasi operasi yang telah Anda lakukan sebelumnya.
  4. Kompilasi bit kode ini ke kode mesin.

Untuk lebih lanjut, makalah ini sangat mudah diakses dan sangat menarik.

Sebagai penutup: kami menggunakan compiler RPython-to-C (atau platform target lainnya) PyPy untuk mengkompilasi interpreter yang diimplementasikan RPython PyPy.

Membungkus

Setelah perbandingan panjang implementasi Python, saya harus bertanya pada diri sendiri: Mengapa ini begitu hebat? Mengapa ide gila ini layak untuk dikejar? Saya pikir Alex Gaynor meletakkannya dengan baik di blognya: “[PyPy adalah masa depan] karena [itu] menawarkan kecepatan yang lebih baik, lebih banyak fleksibilitas, dan merupakan platform yang lebih baik untuk pertumbuhan Python.”

Pendeknya:

  • Ini cepat karena mengkompilasi kode sumber ke kode asli (menggunakan JIT).
  • Ini fleksibel karena menambahkan JIT ke juru bahasa Anda dengan sedikit pekerjaan tambahan.
  • Ini fleksibel (sekali lagi) karena Anda dapat menulis juru bahasa Anda di RPython , yang lebih mudah diperluas daripada, katakanlah, C (sebenarnya, ini sangat mudah sehingga ada tutorial untuk menulis juru bahasa Anda sendiri).

Lampiran: Nama Python Lain yang Mungkin Pernah Anda Dengar

  • Python 3000 (Py3k): penamaan alternatif untuk Python 3.0, rilis Python utama yang tidak kompatibel dengan versi sebelumnya yang diluncurkan pada tahun 2008. Tim Py3k memperkirakan bahwa dibutuhkan waktu sekitar lima tahun agar versi baru ini dapat diadopsi sepenuhnya. Dan sementara sebagian besar (peringatan: klaim anekdot) pengembang Python terus menggunakan Python 2.x, orang-orang semakin sadar akan Py3k.

  • Cython: superset Python yang menyertakan binding untuk memanggil fungsi C.
    • Sasaran: memungkinkan Anda menulis ekstensi C untuk kode Python Anda.
    • Juga memungkinkan Anda menambahkan pengetikan statis ke kode Python yang ada, memungkinkannya untuk dikompilasi dan mencapai kinerja seperti C.
    • Ini mirip dengan PyPy, tetapi tidak sama. Dalam hal ini, Anda memaksa mengetikkan kode pengguna sebelum meneruskannya ke kompiler. Dengan PyPy, Anda menulis Python lama, dan kompilator menangani pengoptimalan apa pun.

  • Numba: “kompiler khusus tepat waktu” yang menambahkan JIT ke kode Python beranotasi . Dalam istilah yang paling dasar, Anda memberikan beberapa petunjuk, dan mempercepat bagian dari kode Anda. Numba hadir sebagai bagian dari distribusi Anaconda, satu set paket untuk analisis dan manajemen data.

  • IPython: sangat berbeda dari apa pun yang dibahas. Lingkungan komputasi untuk Python. Interaktif dengan dukungan untuk toolkit GUI dan pengalaman browser, dll.

  • Psyco: modul ekstensi Python, dan salah satu upaya JIT Python awal. Namun, sejak itu telah ditandai sebagai "tidak terawat dan mati". Faktanya, pengembang utama Psyco, Armin Rigo, sekarang bekerja di PyPy.

Binding Bahasa Python

  • RubyPython: jembatan antara VM Ruby dan Python. Memungkinkan Anda untuk menyematkan kode Python ke dalam kode Ruby Anda. Anda menentukan di mana Python mulai dan berhenti, dan RubyPython menyusun data di antara VM.

  • PyObjc: ikatan bahasa antara Python dan Objective-C, bertindak sebagai jembatan di antara mereka. Praktis, itu berarti Anda dapat menggunakan perpustakaan Objective-C (termasuk semua yang Anda butuhkan untuk membuat aplikasi OS X) dari kode Python Anda, dan modul Python dari kode Objective-C Anda. Dalam hal ini, lebih mudah bahwa CPython ditulis dalam C, yang merupakan bagian dari Objective-C.

  • PyQt: sementara PyObjc memberi Anda pengikatan untuk komponen GUI OS X, PyQt melakukan hal yang sama untuk kerangka kerja aplikasi Qt, memungkinkan Anda membuat antarmuka grafis yang kaya, mengakses database SQL, dll. Alat lain yang ditujukan untuk menghadirkan kesederhanaan Python ke kerangka kerja lain.

Kerangka JavaScript

  • pyjs (Piyama): kerangka kerja untuk membuat aplikasi web dan desktop dengan Python. Termasuk kompiler Python-ke-JavaScript, set widget, dan beberapa alat lainnya.

  • Brython: VM Python yang ditulis dalam JavaScript untuk memungkinkan kode Py3k dieksekusi di browser.