Puoi aggiungere nuove istruzioni alla sintassi di Python?


124

Puoi aggiungere nuove dichiarazioni (come print, raise, with) per la sintassi di Python?

Dì, per consentire ..

mystatement "Something"

O,

new_if True:
    print "example"

Non tanto se dovresti , ma piuttosto se è possibile (a meno di modificare il codice degli interpreti Python)


10
In una nota un po 'correlata, un caso d'uso in cui potrebbe essere utile creare nuove dichiarazioni al volo (invece di "estendere" seriamente la lingua) è per le persone che usano l'interprete interattivo come una calcolatrice, o anche una shell del sistema operativo . Spesso creo piccole funzioni usa e getta al volo per fare qualcosa che ripeterò, e in quelle situazioni sarebbe bello creare comandi molto abbreviati come macro o istruzioni piuttosto che digitare i nomi lunghi con la sintassi function (). Ovviamente non è proprio a questo che serve Py .. ma le persone passano molto tempo a usarlo in modo interattivo.
Chilo

5
@Kilo potrebbe valere la pena dare un'occhiata a ipython - ha molte caratteristiche shell'ish, per esempio puoi usare i normali comandi "ls" e "cd", completamento con tabulazione, molte funzionalità macro ecc.
dbr

Alcuni linguaggi sono squisitamente estensibili, ad esempio Forth e Smalltalk, ma i loro paradigmi linguistici sono anche diversi da quelli usati da Python. Con entrambe queste nuove parole (Forth) o metodi (Smalltalk) diventano parte integrante e indistinguibile del linguaggio per quell'installazione. Quindi ogni installazione Forth o Smalltalk diventa una creazione unica nel tempo. Anche Forth è basato su RPN. Ma pensando sulla falsariga dei DSL, qualcosa di simile dovrebbe essere realizzabile in Python. Tuttavia, come altri hanno detto qui, perché?

1
Essendo una persona fluente sia in Python che in Forth, e che ha implementato diversi compilatori Forth negli anni passati, posso contribuire qui con un certo grado di autorità. Senza ottenere l'accesso grezzo al parser interno di Python, è completamente impossibile. Puoi falsificarlo pre-elaborandolo, come illustrano le risposte (francamente, piuttosto sdrucciolevoli!) Di seguito, ma non è possibile aggiornare veramente la sintassi e / o la semantica della lingua in un interprete caldo. Questa è sia la maledizione di Python che il suo vantaggio rispetto ai linguaggi Lisp e Forth.
Samuel A. Falvo II

Risposte:


153

Potresti trovarlo utile - Python internals: aggiunta di una nuova istruzione a Python , citata qui:


Questo articolo è un tentativo di capire meglio come funziona il front-end di Python. La semplice lettura della documentazione e del codice sorgente può essere un po 'noiosa, quindi sto adottando un approccio pratico qui: aggiungerò untilun'istruzione a Python.

Tutta la codifica per questo articolo è stata eseguita contro il ramo Py3k all'avanguardia nel mirror del repository Python Mercurial .

La untildichiarazione

Alcuni linguaggi, come Ruby, hanno untilun'istruzione, che è il complemento di while( until num == 0è equivalente a while num != 0). In Ruby posso scrivere:

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

E stamperà:

3
2
1

Quindi, voglio aggiungere una funzionalità simile a Python. Cioè, essere in grado di scrivere:

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

Una digressione sulla difesa della lingua

Questo articolo non cerca di suggerire l'aggiunta di untilun'istruzione a Python. Anche se penso che una dichiarazione del genere renderebbe più chiaro un po 'di codice, e questo articolo mostra quanto sia facile aggiungere, rispetto completamente la filosofia del minimalismo di Python. Tutto quello che sto cercando di fare qui, in realtà, è acquisire una visione del funzionamento interno di Python.

Modificare la grammatica

Python utilizza un generatore di parser personalizzato denominato pgen. Questo è un parser LL (1) che converte il codice sorgente Python in un albero di analisi. L'input per il generatore di parser è il file Grammar/Grammar[1] . Questo è un semplice file di testo che specifica la grammatica di Python.

[1] : Da qui in poi, i riferimenti ai file nel sorgente Python vengono forniti relativamente alla radice dell'albero dei sorgenti, che è la directory in cui si esegue configure e make per compilare Python.

È necessario apportare due modifiche al file della grammatica. Il primo è aggiungere una definizione per l' untilaffermazione. Ho trovato dove è whilestata definita l' istruzione ( while_stmt) e aggiunta di until_stmtseguito [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] : Questo dimostra una tecnica comune che uso quando modifico il codice sorgente che non conosco: lavorare per somiglianza . Questo principio non risolverà tutti i tuoi problemi, ma può sicuramente facilitare il processo. Poiché tutto ciò per cui deve essere fatto whiledeve essere fatto anche per until, funge da linea guida piuttosto buona.

Nota che ho deciso di escludere la elseclausola dalla mia definizione di until, solo per renderla un po 'diversa (e perché francamente non mi piace la elseclausola dei cicli e non penso che si adatti bene allo Zen di Python).

La seconda modifica consiste nel modificare la regola per compound_stmtincludere until_stmt, come puoi vedere nello snippet sopra. È subito dopo while_stmt, di nuovo.

Quando si esegue makedopo la modifica Grammar/Grammar, notare che il pgenprogramma viene eseguito per rigenerare Include/graminit.he Python/graminit.c, quindi diversi file vengono ricompilati.

Modifica del codice di generazione AST

Dopo che il parser Python ha creato un albero di analisi, questo albero viene convertito in un AST, poiché è molto più semplice lavorare con gli AST nelle fasi successive del processo di compilazione.

Quindi, visiteremo Parser/Python.asdlche definisce la struttura degli AST di Python e aggiungeremo un nodo AST per la nostra nuova untilistruzione, di nuovo proprio sotto while:

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

Se ora esegui make, nota che prima di compilare un gruppo di file, Parser/asdl_c.pyviene eseguito per generare codice C dal file di definizione AST. Questo (come Grammar/Grammar) è un altro esempio del codice sorgente Python che utilizza un mini-linguaggio (in altre parole, un DSL) per semplificare la programmazione. Si noti inoltre che poiché Parser/asdl_c.pyè uno script Python, questa è una sorta di bootstrap : per costruire Python da zero, Python deve già essere disponibile.

Durante la Parser/asdl_c.pygenerazione del codice per gestire il nostro nodo AST appena definito (nei file Include/Python-ast.he Python/Python-ast.c), dobbiamo ancora scrivere manualmente il codice che converte un nodo di parse-tree rilevante in esso. Questo viene fatto nel file Python/ast.c. Lì, una funzione denominata ast_for_stmtconverte i nodi dell'albero di analisi per le istruzioni in nodi AST. Di nuovo, guidati dal nostro vecchio amico while, saltiamo subito alla grande switchper la gestione delle dichiarazioni composte e aggiungiamo una clausola per until_stmt:

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

Ora dovremmo implementare ast_for_until_stmt. Ecco qui:

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

Di nuovo, questo è stato codificato osservando attentamente l'equivalente ast_for_while_stmt, con la differenza che untilho deciso di non supportare la elseclausola. Come previsto, l'AST viene creato in modo ricorsivo, utilizzando altre funzioni di creazione AST come ast_for_exprper l'espressione della condizione e ast_for_suiteper il corpo untildell'istruzione. Infine, Untilviene restituito un nuovo nodo denominato .

Si noti che accediamo al nodo dell'albero sintetico nutilizzando alcune macro come NCHe CHILD. Vale la pena comprenderli: il loro codice è in Include/node.h.

Digressione: composizione AST

Ho scelto di creare un nuovo tipo di AST per l' untilistruzione, ma in realtà non è necessario. Avrei potuto risparmiare un po 'di lavoro e implementare la nuova funzionalità utilizzando la composizione dei nodi AST esistenti, poiché:

until condition:
   # do stuff

È funzionalmente equivalente a:

while not condition:
  # do stuff

Invece di creare il Untilnodo in ast_for_until_stmt, avrei potuto creare un Notnodo con un Whilenodo da bambino. Poiché il compilatore AST sa già come gestire questi nodi, i passaggi successivi del processo potrebbero essere saltati.

Compilazione di AST in bytecode

Il passaggio successivo è la compilazione dell'AST in bytecode Python. La compilazione ha un risultato intermedio che è un CFG (Control Flow Graph), ma poiché lo gestisce lo stesso codice, per ora ignorerò questo dettaglio e lo lascerò per un altro articolo.

Il codice che vedremo dopo è Python/compile.c. Seguendo l'esempio di while, troviamo la funzione compiler_visit_stmt, che è responsabile della compilazione delle istruzioni in bytecode. Aggiungiamo una clausola per Until:

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

Se ti chiedi cosa Until_kindsia, è una costante (in realtà un valore _stmt_kinddell'enumerazione) generata automaticamente dal file di definizione AST in Include/Python-ast.h. Ad ogni modo, chiamiamo compiler_untilche, ovviamente, ancora non esiste. Ci arrivo un attimo.

Se sei curioso come me, noterai che compiler_visit_stmtè strano. Nessuna quantità di grep-ping dell'albero dei sorgenti rivela dove viene chiamato. Quando questo è il caso, rimane solo un'opzione: C macro-fu. Infatti, una breve indagine ci conduce alla VISITmacro definita in Python/compile.c:

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

È usato per invocare compiler_visit_stmtin compiler_body. Ma torniamo al nostro lavoro ...

Come promesso, ecco 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;
}

Ho una confessione da fare: questo codice non è stato scritto sulla base di una profonda comprensione del bytecode Python. Come il resto dell'articolo, è stato fatto a imitazione della compiler_whilefunzione kin . Leggendolo attentamente, tuttavia, tenendo presente che la VM Python è basata su stack e dando uno sguardo alla documentazione del dismodulo, che ha un elenco di bytecode Python con descrizioni, è possibile capire cosa sta succedendo.

Ecco, abbiamo finito ... non è vero?

Dopo aver apportato tutte le modifiche ed eseguito make, possiamo eseguire il Python appena compilato e provare la nostra nuova untilistruzione:

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

Voilà, funziona! Vediamo il bytecode creato per la nuova istruzione utilizzando il dismodulo come segue:

import dis

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

dis.dis(myfoo)

Ecco il risultato:

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

L'operazione più interessante è la numero 12: se la condizione è vera, saltiamo dopo il ciclo. Questa è la semantica corretta per until. Se il salto non viene eseguito, il corpo del loop continua a correre finché non torna alla condizione dell'operazione 35.

Sentendomi bene per la mia modifica, ho quindi provato a eseguire la funzione (esecuzione myfoo(3)) invece di mostrare il suo bytecode. Il risultato è stato tutt'altro che incoraggiante:

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 ... questo non può essere buono. Allora cosa è andato storto?

Il caso della tabella dei simboli mancante

Uno dei passaggi che il compilatore Python esegue durante la compilazione dell'AST è la creazione di una tabella dei simboli per il codice che compila. La chiamata a PySymtable_Buildin PyAST_Compilechiama nel modulo tabella dei simboli (Python/symtable.c ), che percorre l'AST in un modo simile alle funzioni di generazione del codice. Avere una tabella dei simboli per ogni ambito aiuta il compilatore a capire alcune informazioni chiave, come quali variabili sono globali e quali sono locali rispetto a un ambito.

Per risolvere il problema, dobbiamo modificare la symtable_visit_stmtfunzione in Python/symtable.c, aggiungendo codice per la gestione delle untilistruzioni, dopo il codice simile per le whileistruzioni [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] : A proposito, senza questo codice c'è un avviso del compilatore per Python/symtable.c. Il compilatore nota che il Until_kindvalore di enumerazione non viene gestito nell'istruzione switch di symtable_visit_stmte si lamenta. È sempre importante controllare gli avvisi del compilatore!

E ora abbiamo davvero finito. La compilazione dell'origine dopo questa modifica rende l'esecuzione del myfoo(3)lavoro come previsto.

Conclusione

In questo articolo ho dimostrato come aggiungere una nuova istruzione a Python. Sebbene richiedesse un po 'di armeggiare nel codice del compilatore Python, la modifica non è stata difficile da implementare, perché ho usato un'istruzione simile ed esistente come linea guida.

Il compilatore Python è un sofisticato pezzo di software e non pretendo di essere un esperto in esso. Tuttavia, sono molto interessato agli interni di Python, e in particolare al suo front-end. Pertanto, ho trovato questo esercizio un compagno molto utile per lo studio teorico dei principi del compilatore e del codice sorgente. Servirà come base per articoli futuri che approfondiranno il compilatore.

Riferimenti

Ho usato alcuni ottimi riferimenti per la costruzione di questo articolo. Eccoli senza alcun ordine particolare:

  • PEP 339: Design del compilatore CPython - probabilmente la parte più importante e completa della documentazione ufficiale per il compilatore Python. Essendo molto breve, mostra dolorosamente la scarsità di una buona documentazione degli interni di Python.
  • "Python Compiler Internals" - un articolo di Thomas Lee
  • "Python: Design and Implementation" - una presentazione di Guido van Rossum
  • Python (2.5) Virtual Machine, Una visita guidata - una presentazione di Peter Tröger

fonte originale


7
Ottimo articolo (/ blog), grazie! Accetto poiché questo risponde perfettamente alla domanda e le risposte "non farlo" / "codifica: mylang" sono già molto votate, quindi appariranno piacevolmente nell'ordine \ o /
dbr

1
Ma sfortunatamente, questa non è una risposta. L'articolo collegato è, ma non puoi votare o accettare. Le risposte costituite interamente da un semplice collegamento sono sconsigliate.
Alfe

6
@Alfe: questo è stato pubblicato due anni fa, accettato e fatto +1 da 16 lettori. Nota che si collega al mio post sul blog e la copia di un articolo di grandi dimensioni in StackOverflow non è qualcosa che intendo fare. Sentiti libero di farlo in una modifica utile, piuttosto che giocare alla polizia.
Eli Bendersky

2
@EliBendersky Utile è un eufemismo per quell'articolo. Grazie per aver spiegato così tanto su come funzionano effettivamente queste cose in Python. Questo mi ha davvero aiutato a capire l'AST, che è rilevante per il mio lavoro attuale. ** inoltre, nel caso foste curiosi, la mia versione di untilè isa/ isancome if something isa dict:oif something isan int:
Inversus

5
Quindi, questa risposta è "Scrivi e compila la tua lingua dal sorgente, biforcuta da python"
ThorSummoner

53

Un modo per fare cose del genere è preelaborare il sorgente e modificarlo, traducendo l'istruzione aggiunta in python. Ci sono vari problemi che questo approccio porterà, e non lo consiglierei per un uso generale, ma per la sperimentazione con il linguaggio o la metaprogrammazione per scopi specifici, può essere utile occasionalmente.

Ad esempio, diciamo di voler introdurre un'istruzione "myprint", che invece di stampare sullo schermo registra invece un file specifico. vale a dire:

myprint "This gets logged to file"

sarebbe equivalente a

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

Ci sono varie opzioni su come eseguire la sostituzione, dalla sostituzione dell'espressione regolare alla generazione di un AST, alla scrittura del proprio parser a seconda di quanto la sintassi corrisponda a Python esistente. Un buon approccio intermedio consiste nell'usare il modulo tokenizer. Ciò dovrebbe consentire di aggiungere nuove parole chiave, strutture di controllo ecc. Mentre si interpreta il sorgente in modo simile all'interprete Python, evitando così la rottura che le soluzioni regex crude causerebbero. Per il "myprint" di cui sopra, potresti scrivere il seguente codice di trasformazione:

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

(Questo rende myprint effettivamente una parola chiave, quindi l'uso come variabile altrove probabilmente causerà problemi)

Il problema quindi è come usarlo in modo che il tuo codice sia utilizzabile da Python. Un modo sarebbe semplicemente scrivere la tua funzione di importazione e usarla per caricare il codice scritto nella tua lingua personalizzata. vale a dire:

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

Tuttavia, ciò richiede di gestire il codice personalizzato in modo diverso dai normali moduli Python. cioè " some_mod = myimport("some_mod.py")" invece di " import some_mod"

Un'altra soluzione abbastanza chiara (anche se hacky) è creare una codifica personalizzata (vedere PEP 263 ) come dimostra questa ricetta. Puoi implementarlo come:

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)

Ora, dopo che questo codice è stato eseguito (ad esempio, puoi inserirlo nel tuo .pythonrc o site.py) qualsiasi codice che inizia con il commento "# coding: mylang" verrà automaticamente tradotto tramite il passaggio di preelaborazione precedente. per esempio.

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

Avvertenze:

Ci sono problemi con l'approccio del preprocessore, come probabilmente avrai familiarità se hai lavorato con il preprocessore C. Il principale è il debug. Tutto ciò che Python vede è il file preelaborato, il che significa che il testo stampato nella traccia dello stack ecc. Farà riferimento a quello. Se hai eseguito una traduzione significativa, questa potrebbe essere molto diversa dal testo di partenza. L'esempio sopra non cambia i numeri di riga ecc., Quindi non sarà troppo diverso, ma più lo cambi, più difficile sarà capirlo.


12
Ben fatto! Invece di dire "non può essere schiacciato", in realtà dai alcune buone risposte (che si riducono a "non vuoi davvero farlo").
c0m4

Non sono sicuro di aver capito come funziona il primo esempio - provare a usarlo myimportsu un modulo che contiene semplicemente print 1dato che è solo una riga di codice=1 ... SyntaxError: invalid syntax
olamundo

@noam: non sono sicuro di cosa stia fallendo per te - qui ottengo solo "1" stampato come previsto. (Questo è con i 2 blocchi che iniziano con "import tokenize" e "import new" sopra inseriti nel file a.py, così come " b=myimport("b.py")", e b.py contenente solo " print 1". C'è qualcos'altro nell'errore (stack trace ecc.)?
Brian

3
Python3 non sembra consentirlo, anche se non necessariamente di proposito; Ottengo un errore BOM.
Tobu

nota che importusa il builtin __import__, quindi se lo sovrascrivi ( prima di importare il modulo che richiede l'importazione modificata), non hai bisogno di un separatomyimport
Tobias Kienzler

21

Sì, in una certa misura è possibile. C'è un modulo là fuori che utilizza sys.settrace()per implementare gotoe comefrom"parole chiave":

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
Non è proprio una nuova sintassi però ... sembra proprio così.
Hans Nowak

3
-1: La pagina collegata ha questa intestazione: "Il modulo 'goto' era un pesce d'aprile, pubblicato il 1 ° aprile 2004. Sì, funziona, ma è comunque uno scherzo. Per favore non usarlo in codice reale!"
Jim

6
@ Jim potrebbe riconsiderare un -1. ti suggerisce il meccanismo di implementazione. cosa carina con cui iniziare.
n611x007

14

Breve di cambiare e ricompilare il codice sorgente (che è possibile con l'open source), cambiando la lingua di base non è davvero possibile.

Anche se ricompilassi il sorgente, non sarebbe python, ma solo la tua versione modificata modificata in cui devi stare molto attento a non introdurre bug.

Tuttavia, non sono sicuro del motivo per cui vorresti farlo. Le funzionalità orientate agli oggetti di Python rendono abbastanza semplice ottenere risultati simili con il linguaggio così com'è.


2
Non sono d'accordo su un punto. Se aggiungi nuove parole chiave penso che sarebbe ancora Python. Se modifichi le parole chiave esistenti, allora è solo hackerato, come dici tu.
Bill the Lizard

9
Se aggiungi nuove parole chiave, sarà un linguaggio derivato da Python. Se modifichi le parole chiave, sarebbe un linguaggio incompatibile con Python.
tzot

1
Se aggiungi parole chiave, potresti perdere il punto di "sintassi semplice di facile apprendimento" e "librerie estese". Penso che le caratteristiche del linguaggio siano quasi sempre un errore (gli esempi includono COBOL, Perl e PHP).
S.Lott

5
Nuove parole chiave rompono il codice Python che le utilizza come identificatori.
akaihola

12

Risposta generale: è necessario preelaborare i file sorgente.

Risposta più specifica: installa EasyExtend e segui i passaggi seguenti

i) Crea un nuovo langlet (linguaggio di estensione)

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

Senza ulteriori specifiche, un gruppo di file verrà creato in EasyExtend / langlets / mystmts /.

ii) Apri mystmts / parsedef / Grammar.ext e aggiungi le seguenti righe

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

Questo è sufficiente per definire la sintassi della nuova istruzione. Il non-terminale small_stmt fa parte della grammatica Python ed è il luogo in cui viene agganciata la nuova istruzione. Il parser ora riconoscerà la nuova istruzione, cioè verrà analizzato un file sorgente che la contiene. Il compilatore lo rifiuterà però perché deve ancora essere trasformato in Python valido.

iii) Ora si deve aggiungere la semantica dell'istruzione. Per questo è necessario modificare msytmts / langlet.py e aggiungere un visitatore del nodo 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 in langlets / mystmts e digita

python run_mystmts.py

A questo punto verrà avviata una sessione e sarà possibile utilizzare l'istruzione appena definita:

__________________________________________________________________________________

 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

Pochi passaggi per arrivare a una dichiarazione banale, giusto? Non esiste ancora un'API che consenta di definire cose semplici senza doversi preoccupare della grammatica. Ma EE è molto affidabile modulo alcuni bug. Quindi è solo una questione di tempo che emerge un'API che consente ai programmatori di definire cose convenienti come operatori infissi o piccole istruzioni usando solo una comoda programmazione OO. Per cose più complesse come l'incorporamento di interi linguaggi in Python mediante la creazione di una langlet non c'è modo di aggirare un approccio grammaticale completo.


11

Ecco un modo molto semplice ma schifoso per aggiungere nuove affermazioni, solo in modalità interpretativa . Lo sto usando per piccoli comandi di 1 lettera per la modifica delle annotazioni geniche usando solo sys.displayhook, ma solo per poter rispondere a questa domanda ho aggiunto sys.excepthook anche per gli errori di sintassi. Quest'ultimo è davvero brutto, recupera il codice grezzo dal readline buffer. Il vantaggio è che è banalmente facile aggiungere nuove affermazioni in questo modo.


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

Ho trovato una guida per aggiungere nuove dichiarazioni:

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

Fondamentalmente, per aggiungere nuove istruzioni, è necessario modificare Python/ast.c(tra le altre cose) e ricompilare il binario python.

Sebbene sia possibile, non farlo. Puoi ottenere quasi tutto tramite funzioni e classi (che non richiedono alle persone di ricompilare Python solo per eseguire lo script ..)


Il vero collegamento al PDF - che "autonversion" è interrotto ed è stato interrotto per Dio sa che è ormai lungo: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

È possibile farlo utilizzando EasyExtend :

EasyExtend (EE) è un generatore di preprocessore e framework di metaprogrammazione scritto in puro Python e integrato con CPython. Lo scopo principale di EasyExtend è la creazione di linguaggi di estensione, ovvero l'aggiunta di sintassi e semantica personalizzate a Python.


1
Seguendo quel link ora viene visualizzata una pagina: "EasyExtend è morto. Per coloro che sono interessati a EE c'è un progetto successivo chiamato Langscape Nome diverso, riprogettazione completa, stesso viaggio". Dato che c'è il pericolo che questa pagina di informazioni possa morire, forse è una buona idea aggiornare la risposta.
celtschk


1

Non senza modificare l'interprete. So che molte lingue negli ultimi anni sono state descritte come "estensibili", ma non nel modo in cui le descrivi. Estendi Python aggiungendo funzioni e classi.



1

Alcune cose possono essere fatte con i decoratori. Supponiamo, ad esempio, che Python non avesse withistruzioni. Potremmo quindi implementare un comportamento simile come questo:

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

Tuttavia, è una soluzione piuttosto sporca come fatto qui. In particolare il comportamento in cui il decoratore chiama la funzione e imposta _a Noneè inaspettato. Per chiarimenti: questo decoratore equivale alla scrittura

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

e normalmente ci si aspetta che i decoratori modifichino, non eseguano, funzioni.

Ho usato un tale metodo prima in uno script in cui dovevo impostare temporaneamente la directory di lavoro per diverse funzioni.


0

Dieci anni fa non potevi, e dubito che sia cambiato. Tuttavia, all'epoca non era così difficile modificare la sintassi se eri pronto a ricompilare python, e dubito che sia cambiato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.