Preprosesor Python
(pydong.org)- Python dapat menggunakan komentar ajaib encoding sumber pada dua baris pertama dan codec kustom untuk mengubah isi file sebelum dieksekusi, atau menggantinya sepenuhnya dengan kode lain
- Codec kustom dapat didaftarkan saat inisialisasi interpreter melalui eksekusi
importdari file konfigurasi path.pth, dan menambahkan fungsi pencarian dengancodecs.register - Implementasi codec memerlukan
decode(data: bytes) -> tuple[str, int]dan decoder inkremental; jika exception tidak ditangani, yang mungkin terlihat hanyaSyntaxError: encoding problem: your_codec, bukan penyebabnya - Operator increment/decrement
++/--, Python berbasis kurung kurawal, eksekusi C/C++ lewatcppyy, hingga validasi TOML dengan JSON Schema dapat diimplementasikan melalui entry point yang sama - Selain contoh yang bersifat main-main, ini juga dapat digunakan untuk ekstensi dan backport Python seperti
pythonql,future-typing,future-fstrings,future-annotations;magic_codecmengurangi pekerjaan berulang
Menggunakan encoding sumber sebagai entry point preproses
- Sesuai PEP-0263, salah satu dari dua baris pertama file Python dapat menentukan encoding kode sumber
- Contoh:
# coding=utf8,# -*- coding: utf8 -*-,# vim: set fileencoding=utf8 :
- Contoh:
- Baris ajaib harus cocok dengan regex
^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)- Nama codec harus cocok dengan
[-_.a-zA-Z0-9]+
- Nama codec harus cocok dengan
- Codec kustom tidak hanya mendecode sumber, tetapi juga dapat mengubah string sumber lalu meneruskannya ke interpreter Python
Mendaftarkan codec dengan file .pth
- Jika interpreter Python dijalankan tanpa opsi
-S, paketsitedimuat saat inisialisasi - File konfigurasi path
.pthdi dalamsite-packagesmenambahkan isi selain baris kosong dan baris yang diawali#ke path pencarian modul - Menurut dokumentasi Python, baris yang diikuti spasi atau tab setelah
importakan dieksekusi- Contoh: jika
packagename.pthberisiimport packagename.register_codec, modul tersebut akan di-import saat inisialisasi
- Contoh: jika
- Modul yang di-import dapat memanggil
codecs.registeruntuk mendaftarkan fungsi pencarian codec- Karena import hanya dijalankan sekali, fungsi pencarian juga hanya didaftarkan sekali
Mengimplementasikan codec kustom
- Codec kustom membutuhkan dua hal
decode(data: bytes) -> tuple[str, int]- Kelas decoder inkremental
- Fungsi
decodedapat melakukan decoding UTF-8 sebenarnya dengancodecs.utf_8_decode, lalu meneruskan string hasilnya ke fungsi preproses - Jika exception di dalam codec tidak ditangkap, alih-alih traceback biasa, yang mungkin tercetak hanya
SyntaxError: encoding problem: your_codec- Exception yang terjadi di fungsi preproses sebaiknya dicetak langsung dengan
traceback.print_exc(), lalu di-raise kembali
- Exception yang terjadi di fungsi preproses sebaiknya dicetak langsung dengan
- Decoder inkremental dapat mengumpulkan seluruh file di buffer dan melakukan preproses hanya sekali pada pemanggilan
decodeterakhir- Contoh implementasinya mewarisi
codecs.BufferedIncrementalDecoderdan memproses hanya saatfinalbernilai true didecode(self, data, final=False)
- Contoh implementasinya mewarisi
- Hasil preproses tidak harus menggunakan isi file asli, dan boleh mengembalikan kode Python sepenuhnya arbitrer
- Namun, baris pertama akan dihapus karena dianggap sebagai baris ajaib, dan hasilnya harus berupa Python yang valid
Contoh ekstensi sintaks Python
-
Operator increment dan decrement
++dan--- Python tidak memiliki operator increment/decrement unary
x++,x--tidak valid secara sintaksis++x,--xvalid secara sintaksis, tetapi masing-masing menjadi pemanggilanx.__pos__().__pos__()danx.__neg__().__neg__()- Preprosesor dapat mengubah stream token agar berperilaku seperti operator increment/decrement
x++→(x, x := x + 1)[0]x--→(x, x := x - 1)[0]++x→(x, x := x + 1)[1]--x→(x, x := x - 1)[1]
- Transformasi ini menggunakan operator walrus, yaitu ekspresi assignment Python
- Sekadar substitusi token dapat gagal pada ekspresi seperti
x++ - -y; ambiguitas dapat dikurangi dengan tanda kurung, sepertix++ - (-y) - incdec.py melakukan substitusi dengan regex, tetapi bisa tetap rapuh meski mencoba menghindari substitusi di dalam literal string
- Implementasi yang memodifikasi stream token secara langsung ada di magic.incdec
-
Python berbasis kurung kurawal
from __future__ import bracesmenghasilkanSyntaxError: not a chance- Preprosesor dapat memodifikasi stream token untuk mengubah scope kurung kurawal menjadi Python berbasis indentasi
- Alur implementasinya sebagai berikut
- Menghasilkan token dengan
tokenize.generate_tokens - String input diberikan baris demi baris melalui
readlinedariio.StringIO - Token
INDENTdanDEDENTyang ada dihapus - Saat menemukan
{, level indentasi dinaikkan dan:dicetak - Saat menemukan
}, level indentasi diturunkan - Setelah
NL, tokenINDENTyang sesuai dengan level indentasi saat ini ditambahkan
- Menghasilkan token dengan
- Untuk mengurangi benturan dengan literal dictionary Python, level indentasi dapat disesuaikan hanya ketika setelah
{adalah newline, dan}diperlakukan sebagai penutup scope hanya ketika sebelumnya newline - Dictionary multi-baris dapat digunakan dengan backslash karena tidak menimbulkan token newline di dalam kurung kurawal
Menjalankan bahasa lain sebagai Python
-
C dan C++
- Bahasa seperti shell script, script CMake, PHP, dan Ruby yang memakai
#untuk komentar mudah diberi baris ajaib encoding bersama shebang - C dan C++ memakai komentar
/* ... */atau// ..., tetapi karena directive preprosesor diawali#, keduanya dapat dibuat cocok dengan regex encoding - Contoh baris ajaib berikut valid di sumber C/C++ sekaligus cocok dengan pola encoding Python
#define CODEC "coding:magic.cpp"
- Dengan cppyy, kode C/C++ dapat diinterpretasikan dari Python dan binding dapat dibuat
cppyymenggunakanclingsecara internal
- Hasil preproses kira-kira menjadi kode Python berikut
import cppyycppyy.cppdef("<input source file content>")from cppyy.gbl import main- Memanggil
main()C/C++ saat__name__ == "__main__"
- Contoh implementasinya ada di magic.cpp
- Bahasa seperti shell script, script CMake, PHP, dan Ruby yang memakai
Menggunakannya sebagai alat validasi TOML
- Karena TOML memulai komentar dengan
#, baris ajaib encoding seperti# coding: magic.tomldapat dimasukkan - Jika hasil preproses diganti menjadi skrip validasi Python, interpreter Python dapat digunakan seperti alat validasi TOML
- Contoh validasi menggunakan modul berikut
- Membaca file TOML dengan
tomllib - Membaca file JSON Schema dengan
json - Melakukan validasi dengan jsonschema
- Membaca file TOML dengan
- Contoh eksekusi:
python tests/toml/data_valid.toml -s tests/toml/schema.json- Jika valid, mencetak
Successfully validated.
- Pada contoh TOML yang salah, muncul error validasi bahwa string
'20'di dalam arrayscoresbukan angka - Contoh implementasinya ada di magic.toml
Pemanfaatan nyata dan magic_codec
- Jika codec kustom dan file
.pthdigunakan bersama, perilaku interpreter Python dapat diubah secara signifikan - Sebagian besar contoh dibuat untuk kesenangan, tetapi ada juga kasus penggunaan nyata
- pythonql: ekstensi bahasa query untuk Python
- future-typing: mem-backport type hint generik dan sintaks union
|ke Python 3.6+ - future-fstrings
- future-annotations
- Jika tidak ingin menyentuh
site-packagessecara langsung atau menulis sendiri file.pthdan kode berulang, Anda dapat menggunakan magic_codec - Ekstensi
magic_codecdapat dibuat sebagai paket Python dengan prefiksmagic_- Jika codec file disetel ke
magic_foo, paketmagic_fooakan dimuat - Memeriksa apakah paket tersebut memiliki fungsi
preprocess
- Jika codec file disetel ke
- Signature
preprocessyang diharapkan adalah sebagai berikutdef preprocess(data: str) -> str:
- Contoh ekstensi ada di example/
1 komentar
Komentar Hacker News
Pesan error lucu
SyntaxError: not a chanceyang muncul saat menjalankanfrom __future__ import bracesternyata sudah di-hardcode di CPython sejak 2001https://github.com/python/cpython/commit/ad3d3f2f3f19833f59f...
Penulisnya, Jeremy Hylton, kini bekerja di Google sebagai Principal Engineer yang menangani kualitas pencarian AI. Cukup mengesankan bahwa dalam 24 tahun kariernya berlanjut dari merayakan sintaks terlarang secara iseng ke sistem kueri universal yang tidak membutuhkan sintaks khusus
break rust;yang memicu internal compiler error di compiler Rust. Saya jadi penasaran ada berapa banyak easter egg serupa di bahasa lainMenurut saya keliru jika menganggap hacking hobi yang iseng dan informal sebagai dunia yang terpisah dari pengembangan profesional sungguhan
https://news.ycombinator.com/item?id=41314393
Saya kira bermain-main dengan import hook adalah cara paling kreatif untuk dipecat, tapi ternyata itu pikiran yang naif. Sayang sepertinya regex codec membuat kita tidak bisa benar-benar melakukan trolling dengan sesuatu seperti
μtf8, jadi sekarang tidak ada pilihan selain memakai import hook, preprocessor, dansys.settraceuntuk melakukan monkey patch pada semua fungsi agar menjadi fungsi yang dipanggil tepat sebelumnya, serta menukar stdout dan stderr setiap 17 menitAda alasan bagus mengapa Python sengaja tidak mengekspos hook preprocessor, dan menurut saya orang dewasa yang rasional sebaiknya menjauhinya
Namun di sisi lain, saya tidak ingin berurusan dengan orang dewasa yang rasional. Sepertinya banyak hal yang benar-benar menyenangkan bisa dilakukan
public/private, dan mungkin segala macam sihir metaprogramming juga dibiarkan terbuka. Kalau mereka mengkhawatirkan “orang dewasa yang rasional”, keputusan desain seperti ini akan terasa agak aneh :)Ini terlihat praktis dan sangat berguna. Saat melakukan import hacking yang absurd, biasanya saya mengimpor modul, menulis ulang kodenya dengan modul
ast, laluexecdan menyisipkanexit(); dengan preprocessor, ini akan jauh lebih nyaman dipakaiSebelum semua
dictmenjamin urutan, saya terutama memakainya untuk menulis ulang AST agar literal list diubah menjadi pemanggilan ordered dict, dan itu memang bergunaSaya suka fleksibilitas Python. Hal paling terkutuk yang pernah saya lakukan adalah mengubah string di tempat, dan akhirnya saya sampai menyalahgunakan
mmapuntuk menulis skrip yang memodifikasi dirinya sendiri. Sekarang rasanya saya harus menulis interpreter Lisp sebagai producerctypes?Penggunaan terbaik yang saya temukan sejauh ini adalah pyxl yang terinspirasi JSX: https://github.com/dropbox/pyxl
Kita bisa menulis kode seperti ini
# coding: pyxlprint Hello World!Saya penasaran apakah ini bisa dipakai untuk menangani transisi dari Python 2 ke 3 dengan lebih baik. Misalnya
# coding: six.python2menyesuaikan kode Python 2 agar menjadi kode Python 3 yang valid, atau# coding: six.python3mengubah kode Python 3 agar bisa berjalan di Python 2Sepertinya menambahkan atau menghapus prefiks
b"..."atauu"..."juga mungkin dilakukandictkeduanya menunjuk ke item yang sama; di Python 3,bytesdanstrdengan isi ASCII yang sama menunjuk ke item berbeda dalamdictyang samaAda perubahan yang lebih rumit juga. Banyak fitur bawaan seperti
.keys()dan.values()mengembalikan list di Python 2, tetapi mengembalikan iterator di Python 3. Jika mencoba menerjemahkan kode secara aman dengan utilitassixatau workaround lain, kodenya menjadi sangat verbose. Sebagian besar hanya dipakai sekali, tetapi kadang ada yang dipakai dua kaliJika ada alat yang bisa menulis ulang kode saat import, menurut saya lebih baik commit saja kode yang sudah dikonversi lalu rapikan secara bertahap. Bagian sulitnya adalah perubahan perilaku seperti
strversusbytesyang memengaruhi kode yang berjauhanApakah dependensi yang muncul dari strategi coding hook ini bisa dideteksi oleh
pip freezeatauuv?Kalau tidak, bakal jadi waktu yang menyenangkan :). Jika seseorang sudah memasukkan hal seperti ini, hampir pasti ada jebakan lain juga, jadi rasanya lebih mudah menulis ulang library daripada melawan naga-naga semacam itu
Sepertinya cukup lucu kalau membuat Python pseudocode, lalu membiarkan LLM mendekodenya. Tentu saja bakal mengerikan, tapi mungkin menyenangkan