Python'un sözdizimine yeni ifadeler ekleyebilir misiniz?


124

Yeni ifadeleri (gibi ekleyebilir print, raise, withPython'un sözdizimi)?

İzin vermek için ...

mystatement "Something"

Veya,

new_if True:
    print "example"

O kadar kadar eğer gerektiği , ama mümkün oldukça eğer (kısa piton tercümanlar kodunu değiştirme)


10
Biraz ilgili bir not olarak, anında yeni ifadeler oluşturmanın kullanışlı olabileceği bir kullanım durumu (dili ciddi bir şekilde "genişletmenin" aksine), etkileşimli tercümanı bir hesap makinesi veya hatta bir işletim sistemi kabuğu olarak kullanan kişiler içindir. . Sık sık tekrarlayacağım bir şeyi yapmak için anında küçük işlevler yaratırım ve bu durumlarda uzun isimleri function () sözdizimi ile yazmak yerine makrolar veya ifadeler gibi çok kısaltılmış komutlar oluşturmak güzel olurdu. Tabii ki Py'nin amacı bu değil .. ama insanlar onu interaktif olarak kullanmak için çok zaman harcıyorlar.
Kilo

5
@Kilo, ipython'a bakmaya değer olabilir - birçok shell'ish özelliği vardır, örneğin normal "ls" ve "cd" komutlarını, sekme tamamlamayı, çok sayıda makro benzeri özelliği vb.
Kullanabilirsiniz

Forth ve Smalltalk gibi bazı diller oldukça genişletilebilir ancak dil paradigmaları da Python tarafından kullanılandan farklıdır. Her iki yeni kelime (Forth) veya yöntemler (Smalltalk), bu kurulum için dilin ayrılmaz, ayırt edilemez bir parçası haline gelir. Böylece her Forth veya Smalltalk kurulumu zamanla benzersiz bir yaratıma dönüşür. Ayrıca Forth, RPN tabanlıdır. Ancak DSL'ler doğrultusunda düşünürsek, Python'da bunun gibi bir şey başarılabilir olmalıdır. Yine de, burada başkalarının da söylediği gibi, neden?

1
Hem Python hem de Forth konusunda akıcı olan ve geçmiş yıllarda birkaç Forth derleyicisini uygulayan biri olarak, burada bir dereceye kadar yetki ile katkıda bulunabilirim. Python'un dahili ayrıştırıcısına ham erişim sağlamadan, bu tamamen imkansızdır. Aşağıdaki cevapların da gösterdiği gibi (açıkçası, oldukça kaygan!), Ön işlem yaparak sahte yapabilirsiniz, ancak sıcak bir tercümanda dilin sözdizimini ve / veya anlamını gerçekten güncellemek mümkün değildir. Bu hem Python'un laneti hem de Lisp ve Forth benzeri dillere göre avantajı.
Samuel A. Falvo II

Yanıtlar:


153

Bunu yararlı bulabilirsiniz - Python internals: burada alıntı yapılan Python'a yeni bir ifade ekleme :


Bu makale, Python'un ön ucunun nasıl çalıştığını daha iyi anlamaya yönelik bir girişimdir. Sadece belgeleri ve kaynak kodunu okumak biraz sıkıcı olabilir, bu yüzden burada uygulamalı bir yaklaşım benimsiyorum: untilPython'a bir ifade ekleyeceğim .

Bu makalenin tüm kodlaması, Python Mercurial depo aynasındaki son teknoloji Py3k şubesine karşı yapıldı .

untilaçıklama

Ruby gibi bazı dillerin untiltamamlayıcısı olan while( until num == 0ile eşdeğer olan while num != 0) bir deyimi vardır . Ruby'de şunu yazabilirim:

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

Ve yazdıracak:

3
2
1

Bu yüzden, Python'a benzer bir yetenek eklemek istiyorum. Yani, yazabilmek:

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

Bir dil savunuculuğu araştırması

Bu makale untilPython'a bir ifade eklemeyi önermiyor . Böyle bir ifadenin bazı kodları daha açık hale getireceğini düşünmeme ve bu makale eklemenin ne kadar kolay olduğunu göstermesine rağmen, Python'un minimalizm felsefesine tamamen saygı duyuyorum. Burada gerçekten yapmaya çalıştığım tek şey Python'un iç işleyişi hakkında biraz fikir edinmek.

Dilbilgisini değiştirmek

Python, adında özel bir ayrıştırıcı oluşturucu kullanır pgen. Bu, Python kaynak kodunu bir ayrıştırma ağacına dönüştüren bir LL (1) ayrıştırıcısıdır. Ayrıştırıcı oluşturucunun girdisi Grammar/Grammar[1] dosyasıdır . Bu, Python'un gramerini belirten basit bir metin dosyasıdır.

[1] : Bundan sonra, Python kaynağındaki dosyalara referanslar, Python'u oluşturmak için configure ve make'i çalıştırdığınız dizin olan kaynak ağacının köküne göre verilir.

Dilbilgisi dosyasında iki değişiklik yapılmalıdır. Birincisi, ifade için bir tanım eklemektir until. İfadenin nerede whiletanımlandığını ( while_stmt) buldum ve [2]until_stmt altına ekledim :

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] : Bu, aşina olmadığım kaynak kodunu değiştirirken kullandığım yaygın bir tekniği gösteriyor: benzerlikle çalışmak . Bu ilke tüm sorunlarınızı çözmez, ancak süreci kesinlikle kolaylaştırabilir. Bunun için yapılması gereken her şeyin de yapılması whilegerektiğinden until, oldukça iyi bir kılavuz görevi görür.

Ben dışlamak için karar verdiğiniz Not elsebenim tanımından maddesini untilsadece biraz farklı yapmak için, (ve açıkçası sevmediğim çünkü elsedöngü maddesini ve Python Zen uymaktadır sanmıyorum).

İkinci değişiklik, yukarıdaki kod parçacığında görebileceğiniz gibi compound_stmt, dahil edilecek kuralı değiştirmektir until_stmt. Hemen sonra while_stmt, yine.

Eğer çalıştırdığınızda makeburada geliştirme sonra Grammar/Grammar, bildirim bu pgenprogramı çalıştırılır yeniden üretmek Include/graminit.hve Python/graminit.cve ardından birkaç dosya yeniden derlenmiş olsun.

AST oluşturma kodunu değiştirme

Python ayrıştırıcısı bir ayrıştırma ağacı oluşturduktan sonra, bu ağaç bir AST'ye dönüştürülür, çünkü AST'lerin derleme işleminin sonraki aşamalarında çalışmak çok daha kolaydır .

Öyleyse, Parser/Python.asdlPython'un AST'lerinin yapısını tanımlayan ziyaret edeceğiz ve yeni ifademiz için bir AST düğümü ekleyeceğiz until, yine şunun hemen altına while:

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

Şimdi çalıştırırsanız make, bir grup dosyayı derlemeden önce Parser/asdl_c.py, AST tanım dosyasından C kodu oluşturmak için çalıştırıldığına dikkat edin. Bu (benzeri Grammar/Grammar), programlamayı basitleştirmek için mini bir dil (başka bir deyişle bir DSL) kullanan Python kaynak kodunun başka bir örneğidir. Ayrıca Parser/asdl_c.py, bir Python betiği olduğundan, bunun bir tür önyükleme olduğunu unutmayın - Python'u sıfırdan oluşturmak için, Python'un zaten mevcut olması gerekir.

İken Parser/asdl_c.py(dosyalar halinde bizim yeni tanımlanan AST düğümü yönetmek kodunu oluşturduktan Include/Python-ast.hve Python/Python-ast.c) için de kod yazmak zorunda dönüştürür o elle içine alakalı bir ayrıştırma ağacı düğümü. Bu dosyada yapılır Python/ast.c. Orada, adlı bir işlev ast_for_stmt, ifadeler için ayrıştırma ağaç düğümlerini AST düğümlerine dönüştürür. Yine, eski dostumuzun rehberliğinde while, switchbileşik ifadeleri işlemek için büyüklere atlıyoruz ve aşağıdakiler için bir cümle ekliyoruz until_stmt:

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

Şimdi uygulamalıyız ast_for_until_stmt. İşte burada:

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;
}

Yine, bu, eşdeğerine yakından bakarken kodlandı ast_for_while_stmt, çünkü untilben elsecümleyi desteklememeye karar verdim . Beklendiği gibi, AST, ast_for_exprkoşul ifadesi ve ifadenin ast_for_suitegövdesi gibi diğer AST oluşturma işlevleri kullanılarak yinelemeli olarak oluşturulur until. Son olarak, adlı yeni bir düğüm Untildöndürülür.

Ayrıştırma ağacı düğümüne ve ngibi bazı makroları kullanarak eriştiğimize dikkat edin . Bunlar anlaşılmaya değer - kodları burada .NCHCHILDInclude/node.h

Arasöz: AST bileşimi

İfade için yeni bir AST türü oluşturmayı seçtim until, ancak aslında bu gerekli değil. Bazı işlerden tasarruf edebilir ve mevcut AST düğümlerinin bileşimini kullanarak yeni işlevi uygulayabilirdim, çünkü:

until condition:
   # do stuff

İşlevsel olarak eşdeğerdir:

while not condition:
  # do stuff

UntilDüğümü içinde oluşturmak yerine, çocukken ast_for_until_stmtbir Notdüğümü olan bir Whiledüğüm oluşturabilirdim. AST derleyicisi bu düğümleri nasıl kullanacağını zaten bildiğinden, işlemin sonraki adımları atlanabilir.

AST'leri bayt koduna derleme

Bir sonraki adım, AST'yi Python bayt koduna derlemektir. Derlemenin bir CFG (Kontrol Akışı Grafiği) olan bir ara sonucu vardır, ancak aynı kod onu işlediği için şimdilik bu detayı göz ardı edip başka bir makaleye bırakacağım.

Bundan sonra bakacağımız kod Python/compile.c. Öncülüğünü takiben, ifadeleri bayt koduna derlemekten sorumlu olan whileişlevi buluruz compiler_visit_stmt. Aşağıdakiler için bir madde ekliyoruz Until:

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

Ne olduğunu merak ediyorsanız Until_kind, bu bir sabittir (aslında _stmt_kindnumaralandırmanın bir değeri ), AST tanım dosyasından Include/Python-ast.h. Her neyse, compiler_untiltabii ki hala var olmayan diyoruz . Ona bir an geleceğim.

Benim gibi merak ediyorsanız, compiler_visit_stmtbunun tuhaf olduğunu fark edeceksiniz . grepKaynak ağacının hiçbir miktarı -ping nerede çağrıldığını göstermez. Durum bu olduğunda, geriye yalnızca bir seçenek kalır - C makro-fu. Aslında, kısa bir araştırma bizi VISITşurada tanımlanan makroya götürür Python/compile.c:

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

Çağırmak için kullanılan compiler_visit_stmtiçinde compiler_body. Yine de işimize dönelim ...

Söz verdiğimiz gibi, işte 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;
}

Bir itirafım var: bu kod Python bayt kodunun derinlemesine anlaşılmasına dayalı olarak yazılmadı. Makalenin geri kalanı gibi, kin compiler_whileişlevini taklit ederek yapıldı . Bununla birlikte, dikkatlice okuyarak, Python VM'nin yığın tabanlı olduğunu ve açıklamaları olan Python bayt kodlarının bir listesinidis içeren modülün belgelerine göz atarak , neler olduğunu anlamak mümkündür.

İşte bu, bitirdik ... Değil mi?

Tüm değişiklikleri yaptıktan ve çalıştırdıktan sonra make, yeni derlenmiş Python'u çalıştırabilir ve yeni ifademizi deneyebiliriz until:

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

Voila, işe yarıyor! disModül kullanılarak yeni ifade için oluşturulan bayt kodunu aşağıdaki gibi görelim :

import dis

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

dis.dis(myfoo)

İşte sonuç:

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

En ilginç işlem 12 numara: eğer koşul doğruysa, döngüden sonra atlarız. Bu, için doğru anlambilimdir until. Atlama gerçekleştirilmezse, döngü gövdesi işlem 35'teki duruma geri dönene kadar çalışmaya devam eder.

Yaptığım değişiklikle ilgili iyi hissettiğimden myfoo(3), bayt kodunu göstermek yerine işlevi çalıştırmayı (yürütmeyi ) denedim . Sonuç cesaret verici olmaktan çok uzaktı:

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 ... bu iyi olamaz. Peki yanlış olan ne?

Eksik sembol tablosu durumu

Python derleyicisinin AST'yi derlerken gerçekleştirdiği adımlardan biri, derlediği kod için bir sembol tablosu oluşturmaktır. Çağrısı PySymtable_Buildolarak PyAST_Compilesimge tablo modülü (çağrıda Python/symtable.ckod oluşturma işlevlerine benzer bir şekilde AST yürür). Her kapsam için bir sembol tablosuna sahip olmak, derleyicinin hangi değişkenlerin genel ve hangilerinin bir kapsam için yerel olduğu gibi bazı önemli bilgileri anlamasına yardımcı olur.

Sorunu çözmek için, ifadeler [3] için benzer koddan sonra, ifadeleri işlemek için kod ekleyerek symtable_visit_stmtişlevi değiştirmeliyiz :Python/symtable.cuntilwhile

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] : Bu arada, bu kod olmadan için bir derleyici uyarısı var Python/symtable.c. Derleyici, Until_kindnumaralandırma değerinin switch deyiminde işlenmediğini fark eder symtable_visit_stmtve şikayet eder. Derleyici uyarılarını kontrol etmek her zaman önemlidir!

Ve şimdi gerçekten bitirdik. Bu değişiklikten sonra kaynağı derlemek, myfoo(3)işin beklendiği gibi yürütülmesini sağlar .

Sonuç

Bu makalede Python'a nasıl yeni bir ifade ekleneceğini gösterdim. Python derleyicisinin kodunda epeyce düzeltme gerektirse de, değişikliğin uygulanması zor olmadı çünkü kılavuz olarak benzer ve mevcut bir ifade kullandım.

Python derleyicisi karmaşık bir yazılım parçası ve bu konuda uzman olduğumu iddia etmiyorum. Bununla birlikte, Python'un iç kısımlarıyla ve özellikle ön ucuyla gerçekten ilgileniyorum. Bu nedenle, bu alıştırmayı derleyicinin ilkelerinin ve kaynak kodunun teorik çalışmasında çok yararlı bir yol arkadaşı buldum. Derleyicide daha derinlere inecek olan gelecekteki makaleler için bir temel oluşturacaktır.

Referanslar

Bu makalenin yapımı için birkaç mükemmel referans kullandım. İşte buradalar, belirli bir sıra yok:

  • PEP 339: CPython derleyicisinin tasarımı - Python derleyicisi için muhtemelen en önemli ve kapsamlı resmi dokümantasyon parçası . Çok kısa olması, Python'un iç kısımlarının iyi dokümantasyonunun yetersizliğini acı verici bir şekilde gösterir.
  • "Python Compiler Internals" - Thomas Lee'nin bir makalesi
  • "Python: Tasarım ve Uygulama" - Guido van Rossum'un sunumu
  • Python (2.5) Sanal Makine, Rehberli tur - Peter Tröger'in sunumu

orjinal kaynak


7
Mükemmel makale (/ blog), teşekkürler! Bu, soruyu mükemmel bir şekilde yanıtladığından ve "bunu yapma" / "kodlama: mylang" yanıtlarına zaten yüksek oy verildi, bu nedenle
dbr

1
Ama maalesef bu bir cevap değil. Bağlantılı makale, ancak oy veremez veya kabul edemezsiniz. Tamamen bir bağlantıdan oluşan yanıtlar tavsiye edilmez.
Alfe

6
@Alfe: Bu iki yıl önce yayınlandı, kabul edildi ve 16 okuyucu tarafından + 1'lendi. Kendi blog gönderime bağlandığını ve büyük bir makaleyi StackOverflow'a kopyalamak istediğim bir şey olmadığını unutmayın. Polisle oynamak yerine bunu yararlı bir düzenlemeyle yapmaktan çekinmeyin.
Eli Bendersky

2
@EliBendersky Useful, bu makale için oldukça yetersizdir. Bu şeylerin python'da aslında nasıl çalıştığı hakkında çok fazla açıklama yaptığınız için teşekkürler. Bu, şu anki çalışmamla ilgili olan AST'yi anlamama gerçekten yardımcı oldu. ** ayrıca, eğer, benim sürümü meraklısın untilolduğu isa/ isandeki gibi if something isa dict:veyaif something isan int:
inversuslu

5
Soo, bu cevap "
Python'dan

53

Bunun gibi şeyler yapmanın bir yolu, eklediğiniz ifadeyi python'a çevirerek kaynağı önceden işlemek ve değiştirmektir. Bu yaklaşımın getireceği çeşitli sorunlar vardır ve bunu genel kullanım için tavsiye etmem, ancak dil ile deneyler yapmak veya özel amaçlı meta programlama için ara sıra yararlı olabilir.

Örneğin, ekrana yazdırmak yerine belirli bir dosyaya günlük tutan bir "myprint" ifadesi sunmak istediğimizi varsayalım. yani:

myprint "This gets logged to file"

eşdeğer olacaktır

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

Sözdiziminizin mevcut python ile ne kadar yakın eşleştiğine bağlı olarak, normal ifade değiştirmeden bir AST oluşturmaya, kendi ayrıştırıcınızı yazmaya kadar değiştirmenin nasıl yapılacağına dair çeşitli seçenekler vardır. İyi bir ara yaklaşım, jetonlaştırıcı modülünü kullanmaktır. Bu, kaynağı python yorumlayıcısına benzer şekilde yorumlarken yeni anahtar sözcükler, kontrol yapıları vb. Eklemenize izin vermeli, böylece ham regex çözümlerinin neden olacağı kırılmalardan kaçınılmalıdır. Yukarıdaki "myprint" için aşağıdaki dönüşüm kodunu yazabilirsiniz:

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

(Bu, myprint'i etkili bir şekilde bir anahtar kelime yapar, bu nedenle başka bir yerde değişken olarak kullanmak büyük olasılıkla sorunlara neden olacaktır)

O zaman sorun, kodunuzun python'dan kullanılabilmesi için nasıl kullanılacağıdır. Bunun bir yolu, kendi içe aktarma işlevinizi yazmak ve özel dilinizde yazılmış kodu yüklemek için kullanmaktır. yani:

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

Ancak bu, özelleştirilmiş kodunuzu normal python modüllerinden farklı şekilde işlemenizi gerektirir. yani " some_mod = myimport("some_mod.py")" yerine " import some_mod"

Bu tarifin gösterdiği gibi , oldukça düzgün (hile olsa da) bir başka çözüm, özel bir kodlama oluşturmaktır (Bkz. PEP 263 ) . Bunu şu şekilde uygulayabilirsiniz:

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)

Şimdi bu kod çalıştırıldıktan sonra (örneğin, onu .pythonrc veya site.py dosyanıza yerleştirebilirsiniz) "# kodlama: mylang" yorumuyla başlayan herhangi bir kod, yukarıdaki ön işleme adımıyla otomatik olarak çevrilecektir. Örneğin.

# 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")

Uyarılar:

C ön işlemcisi ile çalıştıysanız muhtemelen aşina olacağınızdan, önişlemci yaklaşımında sorunlar vardır. Bunlardan en önemlisi hata ayıklama. Tüm python'un gördüğü önceden işlenmiş dosyadır, bu da yığın izlemesinde vb. Yazdırılan metnin buna başvuracağı anlamına gelir. Önemli bir çeviri yaptıysanız, bu, kaynak metninizden çok farklı olabilir. Yukarıdaki örnek, satır numaralarını vb. Değiştirmez, bu yüzden çok farklı olmayacaktır, ancak onu ne kadar çok değiştirirseniz, anlamak o kadar zor olacaktır.


12
Güzel bir! 'Dun olamaz' demek yerine, aslında birkaç iyi cevap veriyorsunuz (bu, 'bunu gerçekten yapmak istemezsiniz' anlamına geliyor).
c0m4

İlk örneğin nasıl çalıştığını anladığımdan emin değilim - sadece kod verimi satırını myimportiçeren bir modülü kullanmaya çalışıyorumprint 1=1 ... SyntaxError: invalid syntax
olamundo

@noam: Sizin için neyin başarısız olduğundan emin değilim - burada beklendiği gibi "1" yazdırıyorum. (Bu, yukarıda "içe aktarma belirteci" ve "yeniyi içe aktar" ile başlayan 2 bloğun a.py dosyasına ve " b=myimport("b.py")" ve b.py dosyasında yalnızca " print 1" ifadesini içerdiği durumdur . vb)?
Brian

3
Python3, kasıtlı olmasa da buna izin vermiyor gibi görünüyor; BOM hatası alıyorum.
Tobu

Not importyerleşiğini kullandığı __import__sen (yani üzerine yazarsanız, bu yüzden önce değiştirilmiş ithalat gerektiren modülü ithal), ayrı gerekmezmyimport
Tobias Kienzler

21

Evet, bir dereceye kadar mümkün. Orada uygulamak için ve "anahtar kelimeler" kullanan bir modül var :sys.settrace()gotocomefrom

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"

4
Bu gerçekten yeni bir sözdizimi değil ... sadece öyle görünüyor.
Hans Nowak

3
-1: Bağlantılı sayfanın başlığı şu şekildedir: "'Goto' modülü 1 Nisan 2004'te yayınlanan bir Nisan Şakası şakasıydı. Evet, işe yarıyor, ancak yine de bir şaka. Lütfen gerçek kodda kullanmayın!"
Jim

6
@ Jim bir -1'i yeniden düşünebilir. size uygulama mekanizması hakkında ipuçları verir. başlamak için güzel bir şey.
n611x007

14

Değişen ve (kaynak kodu yeniden derlenerek Kısa olan taban dilini değiştirerek, açık kaynak ile mümkün) gerçekten mümkün değildir.

Kaynağı yeniden derleseniz bile, bu python olmaz, sadece hatalara sokmamak için çok dikkatli olmanız gereken hacklenmiş değiştirilmiş sürümünüz.

Ancak, neden isteyeceğinden emin değilim. Python'un nesne yönelimli özellikleri, mevcut haliyle dil ile benzer sonuçlar elde etmeyi oldukça kolaylaştırır.


2
Bir noktada katılmıyorum. Eğer varsa eklemek yeni anahtar kelimeler ben hala Python olacağını düşünüyorum. Eğer varsa değiştirmek sonra, mevcut anahtar kelimeleri Sadece kesmek-up olduğunu, dediğin gibi.
Bill the Lizard

9
Yeni anahtar kelimeler eklerseniz, bu Python'dan türetilmiş bir dil olacaktır. Anahtar kelimeleri değiştirirseniz, bu Python ile uyumsuz bir dil olacaktır.
tzot

1
Anahtar sözcükler eklerseniz, "öğrenmesi kolay sözdizimi" ve "kapsamlı kitaplıklar" noktasını kaçırıyor olabilirsiniz. Dil özelliklerinin neredeyse her zaman bir hata olduğunu düşünüyorum (örnekler COBOL, Perl ve PHP'yi içerir).
S.Lott

5
Yeni anahtar kelimeler, onları tanımlayıcı olarak kullanan Python kodunu bozar.
akaihola

12

Genel cevap: kaynak dosyalarınızı önceden işlemeniz gerekir.

Daha spesifik cevap: EasyExtend'i kurun ve aşağıdaki adımları izleyin

i) Yeni bir dil (uzantı dili) oluşturun

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

Ek özellikler olmadan EasyExtend / langlets / mystmts / altında bir sürü dosya oluşturulacaktır.

ii) mystmts / parsedef / Grammar.ext'i açın ve aşağıdaki satırları ekleyin

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

Bu, yeni ifadenizin sözdizimini tanımlamak için yeterlidir. Small_stmt non-terminal Python dilbilgisinin bir parçasıdır ve yeni ifadenin bağlandığı yerdir. Ayrıştırıcı şimdi yeni ifadeyi tanıyacak, yani onu içeren bir kaynak dosya ayrıştırılacaktır. Derleyici, yine de geçerli Python'a dönüştürülmesi gerektiği için reddedecektir.

iii) Şimdi ifadenin anlambilimini eklemek gerekir. Bunun için msytmts / langlet.py dosyasını düzenlemek ve bir my_stmt düğüm ziyaretçisi eklemek gerekiyor.

 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'den langlets / mystmts'a ve yazın

python run_mystmts.py

Şimdi bir oturum başlatılacak ve yeni tanımlanan ifade kullanılabilir:

__________________________________________________________________________________

 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

Önemsiz bir ifadeye ulaşmak için oldukça birkaç adım, değil mi? Henüz gramerleri önemsemeden basit şeyleri tanımlamanıza izin veren bir API yok. Ancak EE bazı hatalar için çok güvenilir bir modulo. Bu nedenle, programcıların sadece uygun OO programlamasını kullanarak infix operatörleri veya küçük ifadeler gibi uygun şeyleri tanımlamasına izin veren bir API'nin ortaya çıkması an meselesi. Bir dil oluşturarak tüm dilleri Python'a gömmek gibi daha karmaşık şeyler için tam bir dilbilgisi yaklaşımından geçmenin yolu yoktur.


11

Burada, yalnızca yorumlama modunda yeni ifadeler eklemenin çok basit ama berbat bir yolu var . Sadece sys.displayhook kullanarak gen ek açıklamalarını düzenlemek için küçük 1 harfli komutlar için kullanıyorum, ancak sırf bu soruyu yanıtlayabilmek için sözdizimi hataları için sys.excepthook'u da ekledim. İkincisi gerçekten çirkin, ham kodu okuma satırı tamponundan alıyor. Bunun yararı, bu şekilde yeni ifadeler eklemenin çok kolay olmasıdır.


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

4

Yeni ifadeler eklemek için bir rehber buldum:

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

Temel olarak, yeni ifadeler eklemek için Python/ast.c(diğer şeylerin yanı sıra) python ikilisini düzenlemeniz ve yeniden derlemeniz gerekir.

Mümkün olsa da yapma. Neredeyse her şeyi işlevler ve sınıflar aracılığıyla elde edebilirsiniz (ki bu, insanların sadece komut dosyanızı çalıştırmak için python'u yeniden derlemesini gerektirmez ..)


PDF'ye gerçek bağlantı - bu "otomatik sürüm" bozuldu ve artık Tanrı'nın bildiği için kırıldı: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

Bunu EasyExtend kullanarak yapmak mümkündür :

EasyExtend (EE), saf Python ile yazılmış ve CPython ile entegre edilmiş bir önişlemci üreteci ve metaprogramlama çerçevesidir. EasyExtend'in temel amacı, uzantı dillerinin oluşturulması, yani Python'a özel sözdizimi ve anlambilim eklemektir.


1
Bu bağlantıdan sonra bir sayfa geliyor: "EasyExtend öldü. EE ile ilgilenenler için Langscape adında bir ardıl proje var Farklı isim, tamamen yeniden tasarım, aynı yolculuk." Bu bilgi sayfasının kapanma tehlikesi olduğundan, cevabı güncellemek iyi bir fikir olabilir.
celtschk


1

Tercümanı değiştirmeden olmaz. Geçtiğimiz birkaç yıl içinde birçok dilin "genişletilebilir" olarak tanımlandığını biliyorum, ama sizin tarif ettiğiniz şekilde değil. Python'u işlevler ve sınıflar ekleyerek genişletirsiniz.



1

Dekoratörlerle bazı şeyler yapılabilir. Örneğin, Python'un hiçbir withifadesi olmadığını varsayalım . Daha sonra buna benzer bir davranış uygulayabiliriz:

# ====== 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())

Ancak burada yapıldığı gibi oldukça kirli bir çözümdür. Dekoratör setleri işlevini çağırır ve özellikle davranış _için Nonebeklenmedik bir durumdur. Açıklama için: Bu dekoratör, yazmakla eşdeğerdir

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

ve dekoratörlerin normalde işlevleri yerine getirmeleri değil, değiştirmeleri beklenir.

Böyle bir yöntemi daha önce, birkaç işlev için çalışma dizinini geçici olarak ayarlamam gereken bir komut dosyasında kullandım.


0

On yıl önce yapamazdın ve bunun değiştiğinden şüpheliyim. Bununla birlikte, eğer python'u yeniden derlemeye hazırsanız, sözdizimini değiştirmek o kadar da zor değildi ve bunun da değiştiğinden şüpheliyim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.