Bisakah Anda menambahkan pernyataan baru ke sintaks Python?

124

Dapatkah Anda menambahkan pernyataan baru (seperti print, raise, with) untuk sintaks Python?

Katakan, untuk mengizinkan ..

mystatement "Something"

Atau,

new_if True:
    print "example"

Tidak terlalu banyak jika Anda harus , tetapi jika memungkinkan (singkatnya memodifikasi kode interpreter python)

dbr
sumber
10
Pada catatan yang agak terkait, satu kasus penggunaan di mana mungkin berguna untuk membuat pernyataan baru dengan cepat (berlawanan dengan serius "memperluas" bahasa) adalah untuk orang yang menggunakan penerjemah interaktif sebagai kalkulator, atau bahkan kerangka OS . Saya sering membuat fungsi sekali pakai dengan cepat untuk melakukan sesuatu yang akan saya ulangi, dan dalam situasi tersebut akan menyenangkan untuk membuat perintah yang sangat disingkat seperti makro atau pernyataan daripada mengetik nama panjang dengan sintaks fungsi (). Tentu saja Py bukanlah untuk itu .. tetapi orang-orang menghabiskan banyak waktu untuk menggunakannya secara interaktif.
Kilo
5
@Kilo mungkin layak untuk dilihat di ipython - ia memiliki banyak fitur shell'ish, misalnya Anda dapat menggunakan perintah "ls" dan "cd" biasa, penyelesaian-tab, banyak fitur makro-ish dll
dbr
Beberapa bahasa dapat dikembangkan dengan indah, misalnya Forth dan Smalltalk, tetapi paradigma bahasanya berbeda dari yang digunakan oleh Python juga. Dengan kedua kata baru (Keempat) atau metode (Smalltalk) itu menjadi bagian bahasa yang tidak terpisahkan dan tidak dapat dibedakan untuk instalasi itu. Jadi setiap instalasi Forth atau Smalltalk menjadi kreasi unik dari waktu ke waktu. Keempat juga berbasis RPN. Tetapi berpikir di sepanjang garis DSL, sesuatu seperti ini harus dapat dilakukan dengan Python. Padahal, seperti yang dikatakan orang lain di sini, mengapa?
1
Sebagai seseorang yang fasih dalam Python dan Forth, dan yang telah mengimplementasikan beberapa compiler Forth di tahun-tahun sebelumnya, saya dapat berkontribusi di sini dengan otoritas tertentu. Tanpa mendapatkan akses mentah ke parser internal Python, itu sama sekali tidak mungkin. Anda dapat memalsukannya dengan preprocessing, seperti yang digambarkan oleh jawaban (terus terang, agak licin!) Di bawah, tetapi benar-benar memperbarui sintaks dan / atau semantik bahasa dalam hot interpreter tidak dimungkinkan. Ini adalah kutukan Python sekaligus keunggulannya atas bahasa Lisp dan Forth-like.
Samuel A. Falvo II

Jawaban:

153

Anda mungkin menemukan ini berguna - internal Python: menambahkan pernyataan baru ke Python , dikutip di sini:


Artikel ini adalah upaya untuk lebih memahami cara kerja front-end Python. Hanya membaca dokumentasi dan kode sumber mungkin sedikit membosankan, jadi saya melakukan pendekatan langsung di sini: Saya akan menambahkan untilpernyataan ke Python.

Semua pengkodean untuk artikel ini dilakukan pada cabang Py3k yang mutakhir di cermin repositori Python Mercurial .

The untilpernyataan

Beberapa bahasa, seperti Ruby, memiliki untilpernyataan, yang merupakan pelengkap untuk while( until num == 0setara dengan while num != 0). Di Ruby, saya bisa menulis:

num = 3
until num == 0 do
  puts num
  num -= 1
end

Dan itu akan mencetak:

3
2
1

Jadi, saya ingin menambahkan kemampuan yang mirip dengan Python. Artinya, mampu menulis:

num = 3
until num == 0:
  print(num)
  num -= 1

Penyimpangan advokasi bahasa

Artikel ini tidak mencoba menyarankan penambahan untilpernyataan ke Python. Meskipun menurut saya pernyataan seperti itu akan membuat beberapa kode lebih jelas, dan artikel ini menunjukkan betapa mudahnya menambahkannya, saya sepenuhnya menghormati filosofi minimalisme Python. Semua yang saya coba lakukan di sini, sungguh, adalah mendapatkan beberapa wawasan tentang cara kerja Python.

Mengubah tata bahasa

Python menggunakan generator parser khusus bernama pgen. Ini adalah parser LL (1) yang mengubah kode sumber Python menjadi pohon parse. Input ke generator parser adalah file Grammar/Grammar[1] . Ini adalah file teks sederhana yang menentukan tata bahasa Python.

[1] : Mulai saat ini, referensi ke file dalam sumber Python diberikan secara relatif ke akar dari pohon sumber, yang merupakan direktori tempat Anda menjalankan konfigurasi dan membuat untuk membangun Python.

Dua modifikasi harus dilakukan pada file tata bahasa. Yang pertama adalah menambahkan definisi untuk untilpernyataan tersebut. Saya menemukan di mana whilepernyataan itu didefinisikan ( while_stmt), dan ditambahkan di until_stmtbawah [2] :

compound_stmt: if_stmt | while_stmt | until_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
until_stmt: 'until' test ':' suite

[2] : Ini mendemonstrasikan teknik umum yang saya gunakan saat memodifikasi kode sumber yang tidak saya kenal: bekerja berdasarkan kesamaan . Prinsip ini tidak akan menyelesaikan semua masalah Anda, tetapi pasti dapat mempermudah prosesnya. Karena segala sesuatu yang harus diselesaikan whilejuga harus diselesaikan until, itu berfungsi sebagai pedoman yang cukup baik.

Perhatikan bahwa saya telah memutuskan untuk mengecualikan elseklausa dari definisi saya until, hanya untuk membuatnya sedikit berbeda (dan karena terus terang saya tidak menyukai elseklausa loop dan tidak merasa klausa itu cocok dengan Zen of Python).

Perubahan kedua adalah mengubah aturan untuk compound_stmtdisertakan until_stmt, seperti yang Anda lihat pada cuplikan di atas. Ini tepat setelahnya while_stmt, lagi.

Saat Anda menjalankan makesetelah memodifikasi Grammar/Grammar, perhatikan bahwa pgenprogram dijalankan untuk menghasilkan ulang Include/graminit.hdan Python/graminit.c, kemudian beberapa file akan dikompilasi ulang.

Memodifikasi kode generasi AST

Setelah Python parser membuat pohon parse, pohon ini diubah menjadi AST, karena AST jauh lebih sederhana untuk dikerjakan pada tahap selanjutnya dari proses kompilasi.

Jadi, kita akan mengunjungi Parser/Python.asdlyang mendefinisikan struktur AST Python dan menambahkan simpul AST untuk untilpernyataan baru kita , lagi tepat di bawah while:

| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)

Jika Anda sekarang menjalankan make, perhatikan bahwa sebelum mengkompilasi banyak file, Parser/asdl_c.pydijalankan untuk menghasilkan kode C dari file definisi AST. Ini (suka Grammar/Grammar) adalah contoh lain dari kode sumber Python yang menggunakan bahasa mini (dengan kata lain, DSL) untuk menyederhanakan pemrograman. Perhatikan juga bahwa karena Parser/asdl_c.pyini adalah skrip Python, ini adalah sejenis bootstrap - untuk membuat Python dari awal, Python harus tersedia.

Saat Parser/asdl_c.pymembuat kode untuk mengelola node AST yang baru kami definisikan (ke dalam file Include/Python-ast.hdan Python/Python-ast.c), kami masih harus menulis kode yang mengubah node parse-tree yang relevan ke dalamnya dengan tangan. Ini dilakukan di file Python/ast.c. Di sana, fungsi bernama ast_for_stmtmengubah simpul pohon parse untuk pernyataan menjadi simpul AST. Sekali lagi, dipandu oleh teman lama whilekita, kita langsung terjun ke masalah besar switchuntuk menangani pernyataan majemuk dan menambahkan klausa untuk until_stmt:

case while_stmt:
    return ast_for_while_stmt(c, ch);
case until_stmt:
    return ast_for_until_stmt(c, ch);

Sekarang kita harus menerapkan ast_for_until_stmt. Ini dia:

static stmt_ty
ast_for_until_stmt(struct compiling *c, const node *n)
{
    /* until_stmt: 'until' test ':' suite */
    REQ(n, until_stmt);

    if (NCH(n) == 4) {
        expr_ty expression;
        asdl_seq *suite_seq;

        expression = ast_for_expr(c, CHILD(n, 1));
        if (!expression)
            return NULL;
        suite_seq = ast_for_suite(c, CHILD(n, 3));
        if (!suite_seq)
            return NULL;
        return Until(expression, suite_seq, LINENO(n), n->n_col_offset, c->c_arena);
    }

    PyErr_Format(PyExc_SystemError,
                 "wrong number of tokens for 'until' statement: %d",
                 NCH(n));
    return NULL;
}

Sekali lagi, ini diberi kode sambil melihat dari dekat padanannya ast_for_while_stmt, dengan perbedaan karena untilsaya telah memutuskan untuk tidak mendukung elseklausul tersebut. Seperti yang diharapkan, AST dibuat secara rekursif, menggunakan fungsi pembuatan AST lainnya seperti ast_for_expruntuk ekspresi kondisi dan ast_for_suiteuntuk isi untilpernyataan. Akhirnya, simpul baru bernama Untildikembalikan.

Perhatikan bahwa kami mengakses simpul pohon parse nmenggunakan beberapa makro seperti NCHdan CHILD. Ini layak untuk dipahami - kode mereka ada di dalamnya Include/node.h.

Pencernaan: Komposisi AST

Saya memilih untuk membuat jenis AST baru untuk untilpernyataan tersebut, tetapi sebenarnya ini tidak perlu. Saya bisa menyimpan beberapa pekerjaan dan menerapkan fungsi baru menggunakan komposisi node AST yang ada, karena:

until condition:
   # do stuff

Secara fungsional setara dengan:

while not condition:
  # do stuff

Alih-alih membuat Untilsimpul di ast_for_until_stmt, saya bisa membuat Notsimpul dengan Whilesimpul sebagai anak. Karena kompilator AST sudah mengetahui cara menangani node ini, langkah proses selanjutnya dapat dilewati.

Mengompilasi AST menjadi bytecode

Langkah selanjutnya adalah menyusun AST menjadi bytecode Python. Kompilasi memiliki hasil perantara yang merupakan CFG (Control Flow Graph), tetapi karena kode yang sama menanganinya, saya akan mengabaikan detail ini untuk saat ini dan membiarkannya untuk artikel lain.

Kode yang akan kita lihat selanjutnya adalah Python/compile.c. Mengikuti petunjuk dari while, kami menemukan fungsi compiler_visit_stmt, yang bertanggung jawab untuk menyusun pernyataan menjadi bytecode. Kami menambahkan klausa untuk Until:

case While_kind:
    return compiler_while(c, s);
case Until_kind:
    return compiler_until(c, s);

Jika Anda bertanya-tanya apa Until_kinditu, itu adalah konstanta (sebenarnya nilai _stmt_kindenumerasi) yang secara otomatis dihasilkan dari file definisi AST ke Include/Python-ast.h. Bagaimanapun, kami menyebutnya compiler_untilyang, tentu saja, masih belum ada. Saya akan membahasnya sebentar.

Jika Anda penasaran seperti saya, Anda akan melihat itu compiler_visit_stmtaneh. Tidak ada jumlah grep-ping dari pohon sumber yang mengungkapkan di mana ia dipanggil. Jika demikian, hanya satu opsi yang tersisa - C makro-fu. Memang, investigasi singkat membawa kita ke VISITmakro yang didefinisikan di Python/compile.c:

#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
        return 0; \

Ini digunakan untuk memohon compiler_visit_stmtdi compiler_body. Kembali ke bisnis kami, namun ...

Seperti yang dijanjikan, berikut ini compiler_until:

static int
compiler_until(struct compiler *c, stmt_ty s)
{
    basicblock *loop, *end, *anchor = NULL;
    int constant = expr_constant(s->v.Until.test);

    if (constant == 1) {
        return 1;
    }
    loop = compiler_new_block(c);
    end = compiler_new_block(c);
    if (constant == -1) {
        anchor = compiler_new_block(c);
        if (anchor == NULL)
            return 0;
    }
    if (loop == NULL || end == NULL)
        return 0;

    ADDOP_JREL(c, SETUP_LOOP, end);
    compiler_use_next_block(c, loop);
    if (!compiler_push_fblock(c, LOOP, loop))
        return 0;
    if (constant == -1) {
        VISIT(c, expr, s->v.Until.test);
        ADDOP_JABS(c, POP_JUMP_IF_TRUE, anchor);
    }
    VISIT_SEQ(c, stmt, s->v.Until.body);
    ADDOP_JABS(c, JUMP_ABSOLUTE, loop);

    if (constant == -1) {
        compiler_use_next_block(c, anchor);
        ADDOP(c, POP_BLOCK);
    }
    compiler_pop_fblock(c, LOOP, loop);
    compiler_use_next_block(c, end);

    return 1;
}

Saya harus membuat pengakuan: kode ini tidak ditulis berdasarkan pemahaman mendalam tentang bytecode Python. Seperti artikel lainnya, itu dilakukan dengan meniru compiler_whilefungsi kerabat . Dengan membacanya dengan hati-hati, bagaimanapun, dengan mengingat bahwa VM Python berbasis tumpukan, dan melihat sekilas ke dalam dokumentasi dismodul, yang memiliki daftar bytecode Python dengan deskripsi, mungkin untuk memahami apa yang terjadi.

Itu saja, kita sudah selesai ... bukan?

Setelah membuat semua perubahan dan menjalankan make, kita dapat menjalankan Python yang baru dikompilasi dan mencoba untilpernyataan baru kita :

>>> until num == 0:
...   print(num)
...   num -= 1
...
3
2
1

Voila, berhasil! Mari kita lihat bytecode yang dibuat untuk pernyataan baru dengan menggunakan dismodul sebagai berikut:

import dis

def myfoo(num):
    until num == 0:
        print(num)
        num -= 1

dis.dis(myfoo)

Inilah hasilnya:

4           0 SETUP_LOOP              36 (to 39)
      >>    3 LOAD_FAST                0 (num)
            6 LOAD_CONST               1 (0)
            9 COMPARE_OP               2 (==)
           12 POP_JUMP_IF_TRUE        38

5          15 LOAD_NAME                0 (print)
           18 LOAD_FAST                0 (num)
           21 CALL_FUNCTION            1
           24 POP_TOP

6          25 LOAD_FAST                0 (num)
           28 LOAD_CONST               2 (1)
           31 INPLACE_SUBTRACT
           32 STORE_FAST               0 (num)
           35 JUMP_ABSOLUTE            3
      >>   38 POP_BLOCK
      >>   39 LOAD_CONST               0 (None)
           42 RETURN_VALUE

Operasi yang paling menarik adalah nomor 12: jika kondisinya benar, kita lompat ke setelah pengulangan. Ini adalah semantik yang benar untuk until. Jika lompatan tidak dijalankan, badan loop terus berjalan hingga melompat kembali ke kondisi pada operasi 35.

Merasa senang dengan perubahan saya, saya kemudian mencoba menjalankan fungsi (mengeksekusi myfoo(3)) alih-alih menampilkan bytecode-nya. Hasilnya kurang menggembirakan:

Traceback (most recent call last):
  File "zy.py", line 9, in
    myfoo(3)
  File "zy.py", line 5, in myfoo
    print(num)
SystemError: no locals when loading 'print'

Whoa ... ini tidak bagus. Jadi apa yang salah?

Kasus tabel simbol yang hilang

Salah satu langkah yang dilakukan oleh compiler Python saat mengompilasi AST adalah membuat tabel simbol untuk kode yang dikompilasinya. Panggilan ke PySymtable_Builddalam PyAST_Compilepanggilan ke modul tabel simbol ( Python/symtable.c), yang menjalankan AST dengan cara yang mirip dengan fungsi pembuatan kode. Memiliki tabel simbol untuk setiap ruang lingkup membantu kompilator menemukan beberapa informasi kunci, seperti variabel mana yang global dan yang lokal untuk suatu cakupan.

Untuk memperbaiki masalah ini, kita harus memodifikasi symtable_visit_stmtfungsi in Python/symtable.c, menambahkan kode untuk menangani untilpernyataan, setelah kode serupa untuk whilepernyataan [3] :

case While_kind:
    VISIT(st, expr, s->v.While.test);
    VISIT_SEQ(st, stmt, s->v.While.body);
    if (s->v.While.orelse)
        VISIT_SEQ(st, stmt, s->v.While.orelse);
    break;
case Until_kind:
    VISIT(st, expr, s->v.Until.test);
    VISIT_SEQ(st, stmt, s->v.Until.body);
    break;

[3] : Omong-omong, tanpa kode ini ada peringatan compiler untuk Python/symtable.c. Kompilator memperhatikan bahwa nilai Until_kindenumerasi tidak ditangani dalam pernyataan switch symtable_visit_stmtdan komplain. Selalu penting untuk memeriksa peringatan kompiler!

Dan sekarang kita benar-benar selesai. Mengompilasi sumber setelah perubahan ini membuat eksekusi myfoo(3)pekerjaan seperti yang diharapkan.

Kesimpulan

Dalam artikel ini saya telah mendemonstrasikan cara menambahkan pernyataan baru ke Python. Meskipun membutuhkan sedikit perubahan dalam kode kompiler Python, perubahan itu tidak sulit untuk diterapkan, karena saya menggunakan pernyataan yang serupa dan yang sudah ada sebagai pedoman.

Kompiler Python adalah perangkat lunak yang canggih, dan saya tidak mengklaim sebagai ahli di dalamnya. Namun, saya sangat tertarik dengan bagian dalam Python, dan khususnya bagian depannya. Oleh karena itu, saya menemukan latihan ini sebagai pendamping yang sangat berguna untuk studi teoritis dari prinsip-prinsip kompiler dan kode sumber. Ini akan berfungsi sebagai dasar untuk artikel mendatang yang akan membahas lebih dalam kompiler.

Referensi

Saya menggunakan beberapa referensi bagus untuk konstruksi artikel ini. Di sini mereka tanpa urutan tertentu:

  • PEP 339: Desain kompilator CPython - mungkin bagian paling penting dan komprehensif dari dokumentasi resmi untuk kompilator Python. Singkatnya, ini dengan menyakitkan menampilkan kelangkaan dokumentasi yang baik dari internal Python.
  • "Python Compiler Internals" - artikel oleh Thomas Lee
  • "Python: Desain dan Implementasi" - presentasi oleh Guido van Rossum
  • Mesin Virtual Python (2.5), Tur terpandu - presentasi oleh Peter Tröger

sumber asli

Eli Bendersky
sumber
7
Artikel bagus (/ blog), terima kasih! Menerima karena ini menjawab pertanyaan dengan sempurna, dan jawaban "jangan lakukan itu" / "coding: mylang" sudah sangat disukai, jadi akan muncul dengan baik secara berurutan \ o /
dbr
1
Namun sayangnya, ini bukanlah jawaban. Artikel tertaut adalah, tetapi Anda tidak bisa memberi suara positif atau menerima. Jawaban yang seluruhnya terdiri dari tautan tidak disarankan.
Alfe
6
@Alfe: ini diposting dua tahun lalu, diterima dan diberi +1 oleh 16 pembaca. Perhatikan bahwa ini menautkan ke posting blog saya sendiri, dan menyalin artikel besar ke StackOverflow bukanlah sesuatu yang ingin saya lakukan. Jangan ragu untuk melakukan itu dalam pengeditan yang bermanfaat, daripada bermain sebagai polisi.
Eli Bendersky
2
@EliBendersky Useful adalah pernyataan yang cukup meremehkan artikel itu. Terima kasih telah menjelaskan begitu banyak tentang bagaimana hal-hal ini sebenarnya bekerja dengan python. Ini sangat membantu saya memahami AST, yang relevan dengan pekerjaan saya saat ini. ** juga, jika Anda penasaran, versi saya untiladalah isa/ isanseperti pada if something isa dict:atauif something isan int:
Inversus
5
Jadi, jawaban ini adalah "Tulis dan kompilasi bahasa Anda sendiri dari sumber, bercabang dari python"
ThorSummoner
53

Salah satu cara untuk melakukan hal-hal seperti ini adalah dengan memproses sumber dan memodifikasinya, menerjemahkan pernyataan Anda yang ditambahkan ke python. Ada berbagai masalah yang akan ditimbulkan oleh pendekatan ini, dan saya tidak akan merekomendasikannya untuk penggunaan umum, tetapi untuk eksperimen dengan bahasa, atau metaprogramming dengan tujuan khusus, terkadang dapat berguna.

Misalnya, katakanlah kita ingin memperkenalkan pernyataan "myprint", yang alih-alih mencetak ke layar malah mencatat ke file tertentu. yaitu:

myprint "This gets logged to file"

akan sama dengan

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

Ada berbagai opsi tentang bagaimana melakukan penggantian, dari substitusi regex hingga menghasilkan AST, hingga menulis parser Anda sendiri tergantung pada seberapa dekat sintaks Anda dengan python yang ada. Pendekatan menengah yang baik adalah dengan menggunakan modul tokenizer. Ini memungkinkan Anda untuk menambahkan kata kunci baru, struktur kontrol, dll sambil menafsirkan sumber yang mirip dengan penerjemah python, sehingga menghindari kerusakan yang disebabkan oleh solusi regex mentah. Untuk "myprint" di atas, Anda dapat menulis kode transformasi berikut:

import tokenize

LOGFILE = '/tmp/log.txt'
def translate(readline):
    for type, name,_,_,_ in tokenize.generate_tokens(readline):
        if type ==tokenize.NAME and name =='myprint':
            yield tokenize.NAME, 'print'
            yield tokenize.OP, '>>'
            yield tokenize.NAME, "open"
            yield tokenize.OP, "("
            yield tokenize.STRING, repr(LOGFILE)
            yield tokenize.OP, ","
            yield tokenize.STRING, "'a'"
            yield tokenize.OP, ")"
            yield tokenize.OP, ","
        else:
            yield type,name

(Ini membuat myprint efektif menjadi kata kunci, jadi gunakan sebagai variabel di tempat lain kemungkinan besar akan menyebabkan masalah)

Masalahnya kemudian adalah bagaimana menggunakannya sehingga kode Anda dapat digunakan dari python. Salah satu caranya adalah dengan menulis fungsi impor Anda sendiri, dan menggunakannya untuk memuat kode yang ditulis dalam bahasa kustom Anda. yaitu:

import new
def myimport(filename):
    mod = new.module(filename)
    f=open(filename)
    data = tokenize.untokenize(translate(f.readline))
    exec data in mod.__dict__
    return mod

Ini mengharuskan Anda menangani kode khusus Anda secara berbeda dari modul python normal. yaitu " some_mod = myimport("some_mod.py")" daripada " import some_mod"

Solusi lain yang cukup rapi (meskipun hacky) adalah membuat pengkodean khusus (Lihat PEP 263 ) seperti yang ditunjukkan resep ini . Anda dapat menerapkan ini sebagai:

import codecs, cStringIO, encodings
from encodings import utf_8

class StreamReader(utf_8.StreamReader):
    def __init__(self, *args, **kwargs):
        codecs.StreamReader.__init__(self, *args, **kwargs)
        data = tokenize.untokenize(translate(self.stream.readline))
        self.stream = cStringIO.StringIO(data)

def search_function(s):
    if s!='mylang': return None
    utf8=encodings.search_function('utf8') # Assume utf8 encoding
    return codecs.CodecInfo(
        name='mylang',
        encode = utf8.encode,
        decode = utf8.decode,
        incrementalencoder=utf8.incrementalencoder,
        incrementaldecoder=utf8.incrementaldecoder,
        streamreader=StreamReader,
        streamwriter=utf8.streamwriter)

codecs.register(search_function)

Sekarang setelah kode ini dijalankan (mis. Anda dapat menempatkannya di .pythonrc atau site.py) kode apa pun yang dimulai dengan komentar "# coding: mylang" akan secara otomatis diterjemahkan melalui langkah preprocessing di atas. misalnya.

# coding: mylang
myprint "this gets logged to file"
for i in range(10):
    myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax" 
  "and line continuations")

Peringatan:

Ada masalah dengan pendekatan praprosesor, karena Anda mungkin akan terbiasa jika Anda pernah bekerja dengan praprosesor C. Yang utama adalah debugging. Semua yang dilihat python adalah file yang diproses sebelumnya yang berarti bahwa teks yang dicetak di jejak tumpukan dll akan merujuk ke sana. Jika Anda telah melakukan terjemahan yang signifikan, ini mungkin sangat berbeda dari teks sumber Anda. Contoh di atas tidak mengubah nomor baris dll, jadi tidak akan terlalu berbeda, tetapi semakin banyak Anda mengubahnya, semakin sulit untuk mengetahuinya.

Brian
sumber
12
Bagus! Alih-alih mengatakan 'tidak bisa dilakukan', Anda sebenarnya memberikan beberapa jawaban bagus (yang bermuara pada 'Anda benar-benar tidak ingin melakukan ini').
c0m4
Saya tidak yakin saya mengerti bagaimana contoh pertama bekerja - mencoba menggunakan myimportmodul yang hanya berisi print 1karena itu hanya baris hasil kode=1 ... SyntaxError: invalid syntax
olamundo
@ noam: tidak yakin apa yang gagal untuk Anda - di sini saya hanya mendapatkan "1" dicetak seperti yang diharapkan. (Ini dengan 2 blok yang dimulai "import tokenize" dan "import new" di atas, letakkan di file a.py, serta " b=myimport("b.py")", dan b.py yang hanya berisi " print 1". Apakah ada kesalahan lain (pelacakan tumpukan dll)?
Brian
3
Python3 tampaknya tidak mengizinkan ini, meskipun tidak selalu sengaja; Saya mendapatkan kesalahan BOM.
Tobu
catatan yang importmenggunakan bawaan __import__, jadi jika Anda menimpanya ( sebelum mengimpor modul yang memerlukan impor yang dimodifikasi), Anda tidak memerlukan yang terpisahmyimport
Tobias Kienzler
21

Ya, sampai batas tertentu itu mungkin. Ada sebuah modul di luar sana yang digunakan sys.settrace()untuk mengimplementasikan gotodan comefrom"kata kunci":

from goto import goto, label
for i in range(1, 10):
  for j in range(1, 20):
    print i, j
    if j == 3:
      goto .end # breaking out from nested loop
label .end
print "Finished"
Constantin
sumber
4
Itu bukan sintaks yang benar-benar baru ... hanya terlihat seperti itu.
Hans Nowak
3
-1: Halaman yang ditautkan memiliki judul ini: "Modul 'goto' adalah lelucon April Mop, diterbitkan pada tanggal 1 April 2004. Ya, berfungsi, tapi tetap saja ini lelucon. Mohon jangan gunakan dalam kode sebenarnya!"
Jim
6
@Im mungkin kembali -1. itu mengisyaratkan Anda tentang mekanisme implementasi. hal yang menyenangkan untuk memulai.
n611x007
14

Pendek berubah dan mengkompilasi ulang kode sumber (yang merupakan mungkin dengan open source), mengubah bahasa dasar tidak benar-benar mungkin.

Bahkan jika Anda mengkompilasi ulang sumbernya, itu tidak akan menjadi python, hanya versi peretasan Anda yang diubah yang Anda harus sangat berhati-hati untuk tidak memasukkan bug ke dalamnya.

Namun, saya tidak yakin mengapa Anda menginginkannya. Fitur berorientasi objek Python membuatnya cukup sederhana untuk mencapai hasil yang serupa dengan bahasa yang ada.

paxdiablo
sumber
2
Saya tidak setuju pada satu hal. Jika Anda menambahkan kata kunci baru, saya pikir itu akan tetap Python. Jika Anda mengubah kata kunci yang ada, maka itu baru saja diretas, seperti yang Anda katakan.
Bill the Lizard
9
Jika Anda menambahkan kata kunci baru, itu akan menjadi bahasa turunan Python. Jika Anda mengubah kata kunci, itu akan menjadi bahasa yang tidak kompatibel dengan Python.
tzot
1
Jika Anda menambahkan kata kunci, Anda mungkin kehilangan inti dari "sintaks sederhana yang mudah dipelajari" dan "pustaka ekstensif". Menurut saya fitur bahasa hampir selalu merupakan kesalahan (contohnya termasuk COBOL, Perl dan PHP).
S. Lotot
5
Kata kunci baru akan merusak kode Python yang menggunakannya sebagai pengenal.
akaihola
12

Jawaban umum: Anda perlu memproses file sumber Anda.

Jawaban yang lebih spesifik: instal EasyExtend , dan lakukan langkah-langkah berikut

i) Membuat bahasa baru (bahasa ekstensi)

import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")

Tanpa spesifikasi tambahan, banyak file akan dibuat di EasyExtend / langlets / mystmts /.

ii) Buka mystmts / parsedef / Grammar.ext dan tambahkan baris berikut

small_stmt: (expr_stmt | print_stmt  | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | exec_stmt | assert_stmt | my_stmt )

my_stmt: 'mystatement' expr

Ini cukup untuk menentukan sintaks pernyataan baru Anda. Small_stmt non-terminal adalah bagian dari tata bahasa Python dan itu adalah tempat di mana pernyataan baru dihubungkan. Pengurai sekarang akan mengenali pernyataan baru yaitu file sumber yang berisi itu akan diurai. Kompilator akan menolaknya karena masih harus diubah menjadi Python yang valid.

iii) Sekarang kita harus menambahkan semantik dari pernyataan tersebut. Untuk yang satu ini harus mengedit msytmts / langlet.py dan menambahkan pengunjung simpul my_stmt.

 def call_my_stmt(expression):
     "defines behaviour for my_stmt"
     print "my stmt called with", expression

 class LangletTransformer(Transformer):
       @transform
       def my_stmt(self, node):
           _expr = find_node(node, symbol.expr)
           return any_stmt(CST_CallFunc("call_my_stmt", [_expr]))

 __publish__ = ["call_my_stmt"]

iv) cd ke langlets / mystmts dan ketik

python run_mystmts.py

Sekarang sesi akan dimulai dan pernyataan yang baru didefinisikan dapat digunakan:

__________________________________________________________________________________

 mystmts

 On Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
 __________________________________________________________________________________

 my> mystatement 40+2
 my stmt called with 42

Cukup beberapa langkah untuk sampai pada pernyataan yang sepele, bukan? Belum ada API yang memungkinkan seseorang mendefinisikan hal-hal sederhana tanpa harus peduli dengan tata bahasa. Tapi EE modulo sangat handal beberapa bug. Jadi hanya masalah waktu munculnya API yang memungkinkan pemrogram menentukan hal-hal yang mudah digunakan seperti operator infix atau pernyataan kecil menggunakan pemrograman OO yang nyaman. Untuk hal-hal yang lebih kompleks seperti menyematkan seluruh bahasa dengan Python dengan membuat langlet, tidak ada cara untuk menggunakan pendekatan tata bahasa lengkap.


sumber
11

Inilah cara yang sangat sederhana tapi jelek untuk menambahkan pernyataan baru, dalam mode interpretatif . Saya menggunakannya untuk perintah 1 huruf kecil untuk mengedit anotasi gen hanya menggunakan sys.displayhook, tetapi supaya saya bisa menjawab pertanyaan ini, saya menambahkan sys.excepthook untuk kesalahan sintaks juga. Yang terakhir ini benar-benar jelek, mengambil kode mentah dari buffer readline. Manfaatnya adalah, sangat mudah menambahkan pernyataan baru dengan cara ini.


jcomeau@intrepid:~/$ cat demo.py; ./demo.py
#!/usr/bin/python -i
'load everything needed under "package", such as package.common.normalize()'
import os, sys, readline, traceback
if __name__ == '__main__':
    class t:
        @staticmethod
        def localfunction(*args):
            print 'this is a test'
            if args:
                print 'ignoring %s' % repr(args)

    def displayhook(whatever):
        if hasattr(whatever, 'localfunction'):
            return whatever.localfunction()
        else:
            print whatever

    def excepthook(exctype, value, tb):
        if exctype is SyntaxError:
            index = readline.get_current_history_length()
            item = readline.get_history_item(index)
            command = item.split()
            print 'command:', command
            if len(command[0]) == 1:
                try:
                    eval(command[0]).localfunction(*command[1:])
                except:
                    traceback.print_exception(exctype, value, tb)
        else:
            traceback.print_exception(exctype, value, tb)

    sys.displayhook = displayhook
    sys.excepthook = excepthook
>>> t
this is a test
>>> t t
command: ['t', 't']
this is a test
ignoring ('t',)
>>> ^D
jcomeau_ictx
sumber
4

Saya telah menemukan panduan untuk menambahkan pernyataan baru:

https://troeger.eu/files/teaching/pythonvm08lab.pdf

Pada dasarnya, untuk menambahkan pernyataan baru, Anda harus mengedit Python/ast.c (antara lain) dan mengkompilasi ulang biner python.

Meskipun mungkin, jangan. Anda dapat mencapai hampir semuanya melalui fungsi dan kelas (yang tidak mengharuskan orang untuk mengkompilasi ulang python hanya untuk menjalankan skrip Anda ..)

dbr
sumber
Tautan nyata ke PDF - bahwa "konversi otomatis" rusak dan telah dipecahkan karena Tuhan tahu sekarang: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX
3

Anda dapat melakukan ini menggunakan EasyExtend :

EasyExtend (EE) adalah generator preprocessor dan kerangka kerja metaprogramming yang ditulis dengan Python murni dan terintegrasi dengan CPython. Tujuan utama EasyExtend adalah pembuatan bahasa ekstensi, yaitu menambahkan sintaks dan semantik kustom ke Python.

Matthew Trevor
sumber
1
Mengikuti tautan itu sekarang memberikan halaman: "EasyExtend sudah mati. Bagi mereka yang tertarik dengan EE, ada proyek penerus yang disebut Langscape Nama yang berbeda, desain ulang lengkap, perjalanan yang sama." Karena ada bahaya halaman informasi ini bisa mati, mungkin ada baiknya untuk memperbarui jawabannya.
celtschk
1

Bukan tanpa memodifikasi juru bahasa. Saya tahu banyak bahasa dalam beberapa tahun terakhir telah digambarkan sebagai "dapat diperluas", tetapi tidak seperti yang Anda gambarkan. Anda memperluas Python dengan menambahkan fungsi dan kelas.

Bill the Lizard
sumber
1

Ada bahasa berdasarkan python yang disebut Logix yang dengannya Anda BISA melakukan hal-hal seperti itu. Ini belum dalam pengembangan untuk sementara waktu, tetapi fitur yang Anda minta berfungsi dengan versi terbaru.

Claudiu
sumber
Kedengarannya menarik, tetapi tampaknya telah mati sekitar tahun 2009: web.archive.org/web/20090107014050/http://livelogix.net/logix
Tobias Kienzler
1

Beberapa hal dapat dilakukan dengan dekorator. Mari kita asumsikan, Python tidak memiliki withpernyataan. Kami kemudian dapat menerapkan perilaku serupa seperti ini:

# ====== Implementation of "mywith" decorator ======

def mywith(stream):
    def decorator(function):
        try: function(stream)
        finally: stream.close()
    return decorator

# ====== Using the decorator ======

@mywith(open("test.py","r"))
def _(infile):
    for l in infile.readlines():
        print(">>", l.rstrip())

Ini adalah solusi yang sangat tidak bersih seperti yang dilakukan di sini. Terutama perilaku di mana dekorator memanggil fungsi dan set _ke Nonetidak terduga. Untuk klarifikasi: Dekorator ini setara dengan tulisan

def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.

dan dekorator biasanya diharapkan untuk memodifikasi, bukan menjalankan, fungsi.

Saya menggunakan metode seperti itu sebelumnya dalam skrip di mana saya harus mengatur sementara direktori kerja untuk beberapa fungsi.

kdb
sumber
0

Sepuluh tahun yang lalu Anda tidak bisa, dan saya ragu itu berubah. Namun, tidak sulit untuk memodifikasi sintaks saat itu jika Anda siap untuk mengkompilasi ulang python, dan saya ragu itu juga berubah.

Alex Coventry
sumber