¿Puedes agregar nuevas declaraciones a la sintaxis de Python?

124

Se puede añadir nuevas declaraciones (como print, raise, with) a la sintaxis de Python?

Decir, para permitir ..

mystatement "Something"

O,

new_if True:
    print "example"

No tanto si debería , sino más bien si es posible (salvo modificar el código de los intérpretes de Python)

dbr
fuente
10
En una nota algo relacionada, un caso de uso en el que podría ser útil crear nuevas declaraciones sobre la marcha (en lugar de "extender" seriamente el lenguaje) es para las personas que usan el intérprete interactivo como una calculadora, o incluso un shell del sistema operativo . A menudo creo pequeñas funciones descartables sobre la marcha para hacer algo que voy a repetir, y en esas situaciones sería bueno crear comandos muy abreviados como macros o declaraciones en lugar de escribir los nombres largos con la sintaxis function (). Por supuesto, eso no es realmente para lo que Py es ... pero la gente pasa mucho tiempo usándolo interactivamente.
Kilo el
55
@Kilo podría valer la pena mirar ipython: tiene muchas características de shell, por ejemplo, puede usar comandos regulares "ls" y "cd", completar pestañas, muchas características macro-ish, etc.
dbr
Algunos lenguajes son exquisitamente extensibles, por ejemplo, Forth y Smalltalk, pero sus paradigmas de lenguaje son diferentes a los utilizados por Python también. Con ambos, las nuevas palabras (Forth) o los métodos (Smalltalk) se convierten en una parte integral e indistinguible del lenguaje para esa instalación. Por lo tanto, cada instalación de Forth o Smalltalk se convierte en una creación única con el tiempo. También Forth está basado en RPN. Pero pensando en la línea de DSL, algo así debería ser posible en Python. Aunque, como otros han dicho aquí, ¿por qué?
1
Como alguien con fluidez tanto en Python como en Forth, y que ha implementado varios compiladores de Forth en años anteriores, puedo contribuir aquí con cierto grado de autoridad. Sin obtener acceso sin procesar al analizador interno de Python, es completamente imposible. Puede simularlo mediante el preprocesamiento, como lo ilustran las respuestas (francamente, bastante ingeniosas) a continuación, pero no es posible actualizar realmente la sintaxis y / o la semántica del lenguaje en un intérprete atractivo. Esta es la maldición de Python y su ventaja sobre los lenguajes similares a Lisp y Forth.
Samuel A. Falvo II

Respuestas:

153

Esto puede resultarle útil: aspectos internos de Python: agregar una nueva declaración a Python , citada aquí:


Este artículo es un intento de comprender mejor cómo funciona el front-end de Python. Solo leer la documentación y el código fuente puede ser un poco aburrido, así que estoy adoptando un enfoque práctico aquí: voy a agregar una untildeclaración a Python.

Toda la codificación de este artículo se realizó contra la rama Py3k de última generación en el espejo del repositorio Python Mercurial .

La untildeclaración

Algunos idiomas, como Ruby, tienen una untildeclaración, que es el complemento de while( until num == 0es equivalente a while num != 0). En Ruby, puedo escribir:

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

Y se imprimirá:

3
2
1

Entonces, quiero agregar una capacidad similar a Python. Es decir, poder escribir:

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

Una digresión de defensa del lenguaje

Este artículo no intenta sugerir la adición de una untildeclaración a Python. Aunque creo que tal afirmación aclararía un poco el código, y este artículo muestra lo fácil que es agregarlo, respeto completamente la filosofía de minimalismo de Python. Todo lo que estoy tratando de hacer aquí, realmente, es obtener una idea del funcionamiento interno de Python.

Modificando la gramática

Python usa un generador de analizador personalizado llamado pgen. Este es un analizador LL (1) que convierte el código fuente de Python en un árbol de análisis. La entrada al generador de analizadores es el archivo Grammar/Grammar[1] . Este es un archivo de texto simple que especifica la gramática de Python.

[1] : De aquí en adelante, las referencias a los archivos en la fuente de Python se dan relativamente a la raíz del árbol de fuentes, que es el directorio donde ejecuta configure y crea para construir Python.

Deben realizarse dos modificaciones al archivo de gramática. El primero es agregar una definición para la untildeclaración. Encontré dónde whilese definió la declaración ( while_stmt), y agregué a until_stmtcontinuación [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] : Esto demuestra una técnica común que uso al modificar el código fuente con el que no estoy familiarizado: trabajar por similitud . Este principio no resolverá todos sus problemas, pero definitivamente puede facilitar el proceso. Dado que todo lo que debe hacerse whiletambién debe hacerse until, sirve como una guía bastante buena.

Tenga en cuenta que he decidido excluir la elsecláusula de mi definición de until, solo para que sea un poco diferente (y porque, francamente, no me gusta la elsecláusula de bucles y no creo que encaje bien con el Zen de Python).

El segundo cambio es modificar la regla para compound_stmtincluir until_stmt, como puede ver en el fragmento de arriba. Es justo después while_stmt, de nuevo.

Cuando se ejecuta makedespués de modificar Grammar/Grammar, observe que el pgenprograma se ejecuta para volver a generar Include/graminit.hy Python/graminit.c, a continuación, varios archivos se vuelven a compilar.

Modificando el código de generación AST

Después de que el analizador de Python haya creado un árbol de análisis, este árbol se convierte en un AST, ya que los AST son mucho más simples para trabajar en las etapas posteriores del proceso de compilación.

Entonces, vamos a visitar Parser/Python.asdlqué define la estructura de los AST de Python y agregaremos un nodo AST para nuestra nueva untildeclaración, nuevamente justo debajo de while:

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

Si ahora ejecuta make, tenga en cuenta que antes de compilar un montón de archivos, Parser/asdl_c.pyse ejecuta para generar código C a partir del archivo de definición AST. Este (como Grammar/Grammar) es otro ejemplo del código fuente de Python usando un mini-lenguaje (en otras palabras, un DSL) para simplificar la programación. También tenga en cuenta que, dado que Parser/asdl_c.pyes un script de Python, este es un tipo de arranque : para construir Python desde cero, Python ya tiene que estar disponible.

Si bien Parser/asdl_c.pygeneramos el código para administrar nuestro nodo AST recién definido (en los archivos Include/Python-ast.hy Python/Python-ast.c), todavía tenemos que escribir el código que convierte a mano un nodo de árbol de análisis relevante. Esto se hace en el archivo Python/ast.c. Allí, una función llamada ast_for_stmtconvierte los nodos del árbol de análisis para las declaraciones en nodos AST. Nuevamente, guiados por nuestro viejo amigo while, saltamos directamente a lo grande switchpara manejar declaraciones compuestas y agregamos una cláusula para until_stmt:

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

Ahora deberíamos implementar ast_for_until_stmt. Aquí está:

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

Una vez más, esto fue codificado mientras miraba de cerca el equivalente ast_for_while_stmt, con la diferencia de que untilhe decidido no apoyar la elsecláusula. Como se esperaba, el AST se crea de forma recursiva, utilizando otras funciones de creación de AST, como ast_for_exprla expresión de condición y ast_for_suiteel cuerpo de la untildeclaración. Finalmente, Untilse devuelve un nuevo nodo llamado .

Tenga en cuenta que accedemos al nodo parse-tree nusando algunas macros como NCHy CHILD. Vale la pena entenderlos: su código está en Include/node.h.

Digresión: composición AST

Elegí crear un nuevo tipo de AST para la untildeclaración, pero en realidad esto no es necesario. Podría haber ahorrado algo de trabajo e implementado la nueva funcionalidad usando la composición de los nodos AST existentes, ya que:

until condition:
   # do stuff

Es funcionalmente equivalente a:

while not condition:
  # do stuff

En lugar de crear el Untilnodo ast_for_until_stmt, podría haber creado un Notnodo con un Whilenodo cuando era niño. Como el compilador AST ya sabe cómo manejar estos nodos, se pueden omitir los siguientes pasos del proceso.

Compilación de AST en bytecode

El siguiente paso es compilar el AST en Python bytecode. La compilación tiene un resultado intermedio que es un CFG (Control Flow Graph), pero como el mismo código lo maneja, ignoraré este detalle por ahora y lo dejaré para otro artículo.

El código que veremos a continuación es Python/compile.c. Siguiendo el ejemplo de while, encontramos la función compiler_visit_stmt, que es responsable de compilar las declaraciones en bytecode. Agregamos una cláusula para Until:

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

Si se pregunta qué Until_kindes, es una constante (en realidad un valor de la _stmt_kindenumeración) generada automáticamente desde el archivo de definición AST Include/Python-ast.h. De todos modos, llamamos compiler_untilque, por supuesto, todavía no existe. Llegaré a eso un momento.

Si tienes curiosidad como yo, notarás que compiler_visit_stmtes peculiar. Ninguna cantidad de grep-ping del árbol fuente revela dónde se llama. Cuando este es el caso, solo queda una opción: C macro-fu. De hecho, una breve investigación nos lleva a la VISITmacro definida en Python/compile.c:

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

Se utiliza para invocar compiler_visit_stmten compiler_body. Volviendo a nuestro negocio, sin embargo ...

Según lo prometido, aquí está 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;
}

Tengo una confesión que hacer: este código no fue escrito en base a un profundo conocimiento del código de bytes Python. Al igual que el resto del artículo, se realizó imitando la compiler_whilefunción de parentesco . Sin embargo, al leerlo detenidamente, teniendo en cuenta que la máquina virtual de Python está basada en la pila, y mirando la documentación del dismódulo, que tiene una lista de códigos de bytes de Python con descripciones, es posible comprender lo que está sucediendo.

Eso es todo, hemos terminado ... ¿no?

Después de hacer todos los cambios y ejecutar make, podemos ejecutar el Python recién compilado y probar nuestra nueva untildeclaración:

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

¡Voila, funciona! Veamos el bytecode creado para la nueva declaración usando el dismódulo de la siguiente manera:

import dis

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

dis.dis(myfoo)

Aquí está el resultado:

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

La operación más interesante es la número 12: si la condición es verdadera, saltamos a después del ciclo. Esta es la semántica correcta para until. Si no se ejecuta el salto, el cuerpo del bucle sigue corriendo hasta que vuelve a la condición en la operación 35.

Sintiéndome bien con mi cambio, intenté ejecutar la función (ejecutar myfoo(3)) en lugar de mostrar su código de bytes. El resultado fue menos que alentador:

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 ... esto no puede ser bueno. Entonces, ¿qué salió mal?

El caso de la tabla de símbolos que falta

Uno de los pasos que realiza el compilador de Python al compilar el AST es crear una tabla de símbolos para el código que compila. La llamada a PySymtable_Buildin PyAST_Compilellama al módulo de tabla de símbolos ( Python/symtable.c), que recorre el AST de manera similar a las funciones de generación de código. Tener una tabla de símbolos para cada ámbito ayuda al compilador a encontrar información clave, como qué variables son globales y cuáles son locales para un ámbito.

Para solucionar el problema, tenemos que modificar la symtable_visit_stmtfunción Python/symtable.c, agregando código para manejar las untildeclaraciones, después del código similar para las whiledeclaraciones [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] : Por cierto, sin este código hay una advertencia del compilador Python/symtable.c. El compilador nota que el Until_kindvalor de enumeración no se maneja en la declaración de cambio symtable_visit_stmty se queja. ¡Siempre es importante verificar las advertencias del compilador!

Y ahora realmente hemos terminado. Compilar la fuente después de este cambio hace que la ejecución del myfoo(3)trabajo sea la esperada.

Conclusión

En este artículo, he demostrado cómo agregar una nueva declaración a Python. Aunque requirió un poco de retoques en el código del compilador de Python, el cambio no fue difícil de implementar, porque utilicé una declaración similar y existente como guía.

El compilador de Python es una parte sofisticada de software, y no pretendo ser un experto en él. Sin embargo, estoy realmente interesado en los aspectos internos de Python, y particularmente en su front-end. Por lo tanto, este ejercicio me pareció un compañero muy útil para el estudio teórico de los principios y el código fuente del compilador. Servirá como base para futuros artículos que profundizarán en el compilador.

Referencias

Utilicé algunas referencias excelentes para la construcción de este artículo. Aquí están, sin ningún orden en particular:

  • PEP 339: Diseño del compilador CPython : probablemente la pieza de documentación oficial más importante y completa para el compilador Python. Al ser muy corto, muestra dolorosamente la escasez de buena documentación de los componentes internos de Python.
  • "Compilación interna de Python" - un artículo de Thomas Lee
  • "Python: diseño e implementación" - una presentación de Guido van Rossum
  • Python (2.5) Virtual Machine, una visita guiada - una presentación de Peter Tröger

fuente original

Eli Bendersky
fuente
77
Excelente artículo (/ blog), gracias! Aceptando ya que esto responde perfectamente a la pregunta, y las respuestas "no hagas eso" / "codificación: mylang" ya están altamente votadas, por lo que aparecerán muy bien en orden \ o /
dbr
1
Pero desafortunadamente, esta no es una respuesta. El artículo vinculado es, pero que no puede votar ni aceptar. Se desaconsejan las respuestas que consisten únicamente en un enlace.
Alfe
66
@Alfe: esto fue publicado hace dos años, aceptado y hecho +1 por 16 lectores. Tenga en cuenta que se vincula a mi propia publicación de blog, y copiar un artículo grande en StackOverflow no es algo que intente hacer. Siéntase libre de hacerlo en una edición útil, en lugar de jugar a la policía.
Eli Bendersky
2
@EliBendersky Útil es un eufemismo para ese artículo. Gracias por explicar tanto sobre cómo funcionan estas cosas en Python. Esto realmente me ha ayudado a comprender el AST, que es relevante para mi trabajo actual. ** también, en caso de curiosidad, mi versión de untiles isa/ isanas en if something isa dict:oif something isan int:
Inversus
55
Entonces, esta respuesta es "Escribe y compila tu propio idioma desde la fuente, bifurcado desde python"
ThorSummoner
53

Una forma de hacer cosas como esta es preprocesar la fuente y modificarla, traduciendo su declaración agregada a python. Hay varios problemas que este enfoque traerá, y no lo recomendaría para uso general, pero para la experimentación con el lenguaje o la metaprogramación de propósito específico, ocasionalmente puede ser útil.

Por ejemplo, supongamos que queremos introducir una declaración "myprint" que, en lugar de imprimir en la pantalla, inicie sesión en un archivo específico. es decir:

myprint "This gets logged to file"

sería equivalente a

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

Hay varias opciones sobre cómo hacer el reemplazo, desde la sustitución de expresiones regulares hasta la generación de un AST, hasta escribir su propio analizador dependiendo de qué tan cerca coincida su sintaxis con la pitón existente. Un buen enfoque intermedio es usar el módulo tokenizer. Esto debería permitirle agregar nuevas palabras clave, estructuras de control, etc. al interpretar la fuente de manera similar al intérprete de Python, evitando así la rotura que causarían las soluciones crudas de expresiones regulares. Para el "myprint" anterior, puede escribir el siguiente código de transformación:

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

(Esto hace que myprint sea efectivamente una palabra clave, por lo que su uso como variable en otros lugares probablemente causará problemas)

El problema es cómo usarlo para que su código sea utilizable desde Python. Una forma sería escribir su propia función de importación y usarla para cargar el código escrito en su idioma personalizado. es decir:

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

Sin embargo, esto requiere que maneje su código personalizado de manera diferente a los módulos de Python normales. es decir, " some_mod = myimport("some_mod.py")" en lugar de " import some_mod"

Otra solución bastante clara (aunque hacky) es crear una codificación personalizada (ver PEP 263 ) como lo demuestra esta receta. Puede implementar esto como:

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)

Ahora, después de ejecutar este código (por ejemplo, puede colocarlo en su .pythonrc o site.py), cualquier código que comience con el comentario "# coding: mylang" se traducirá automáticamente a través del paso de preprocesamiento anterior. p.ej.

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

Advertencias:

El enfoque del preprocesador tiene problemas, ya que probablemente estará familiarizado si ha trabajado con el preprocesador C. El principal es la depuración. Todo lo que ve Python es el archivo preprocesado, lo que significa que el texto impreso en el seguimiento de la pila, etc. Si ha realizado una traducción significativa, esto puede ser muy diferente de su texto fuente. El ejemplo anterior no cambia los números de línea, etc., por lo que no será muy diferente, pero cuanto más lo cambie, más difícil será descubrirlo.

Brian
fuente
12
¡Buena esa! En lugar de decir 'no se puede ser tonto', en realidad das algunas buenas respuestas (que se reduce a 'realmente no quieres hacer esto') Voto a favor.
c0m4
No estoy seguro de entender cómo los primeros trabajos ejemplo - tratando de utilizar myimporten un módulo que contiene simplemente print 1, ya que es la única línea de los rendimientos de código=1 ... SyntaxError: invalid syntax
olamundo
@noam: no estoy seguro de qué es lo que te está fallando, aquí solo obtengo "1" impreso como se esperaba. (Esto es con los 2 bloques que comienzan "import tokenize" e "import new" arriba colocados en el archivo a.py, así como " b=myimport("b.py")" y b.py que contienen solo " print 1". ¿Hay algo más en el error? etc)
Brian
3
Python3 no parece permitir esto, aunque no necesariamente a propósito; Me sale un error de lista de materiales.
Tobu
tenga en cuenta que importusa el builtin incorporado __import__, por lo que si sobrescribe eso ( antes de importar el módulo que requiere la importación modificada), no necesita un separadomyimport
Tobias Kienzler
21

Sí, hasta cierto punto es posible. Hay un módulo que se usa sys.settrace()para implementar gotoy comefrom"palabras clave":

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
fuente
44
Sin embargo, esa no es una sintaxis realmente nueva ... simplemente lo parece.
Hans Nowak
3
-1: La página vinculada tiene este encabezado: "El módulo 'goto' fue un chiste de los Inocentes, publicado el 1 de abril de 2004. Sí, funciona, pero es un chiste de todos modos. ¡No lo use en código real!"
Jim
66
@Jim podría reconsiderar un -1. te da una idea del mecanismo de implementación. algo bueno para empezar.
n611x007
14

A falta de cambiar y volver a compilar el código fuente (que es posible con el código abierto), cambiar el idioma base no es realmente posible.

Incluso si vuelve a compilar la fuente, no sería Python, solo su versión modificada pirateada, que debe tener mucho cuidado de no introducir errores.

Sin embargo, no estoy seguro de por qué querrías hacerlo. Las características orientadas a objetos de Python hacen que sea bastante simple lograr resultados similares con el lenguaje tal como está.

paxdiablo
fuente
2
No estoy de acuerdo en un punto. Si agrega nuevas palabras clave, creo que todavía sería Python. Si cambia las palabras clave existentes, entonces eso es simplemente pirateado, como usted dice.
Bill the Lizard
9
Si agrega nuevas palabras clave, sería un lenguaje derivado de Python. Si cambia las palabras clave, sería un lenguaje incompatible con Python.
tzot
1
Si agrega palabras clave, es posible que le falte el punto de "sintaxis simple y fácil de aprender" y "bibliotecas extensas". Creo que las características del lenguaje son casi siempre un error (los ejemplos incluyen COBOL, Perl y PHP).
S.Lott
55
Las nuevas palabras clave romperían el código de Python que las usa como identificadores.
akaihola
12

Respuesta general: debe preprocesar sus archivos fuente.

Respuesta más específica: instalar EasyExtend y siga los siguientes pasos

i) Crear un nuevo langlet (idioma de extensión)

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

Sin una especificación adicional, se creará un conjunto de archivos en EasyExtend / langlets / mystmts /.

ii) Abra mystmts / parsedef / Grammar.ext y agregue las siguientes líneas

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

Esto es suficiente para definir la sintaxis de su nueva declaración. Small_stmt no terminal es parte de la gramática de Python y es el lugar donde se engancha la nueva declaración. El analizador ahora reconocerá la nueva declaración, es decir, se analizará un archivo fuente que la contenga. Sin embargo, el compilador lo rechazará porque todavía tiene que transformarse en Python válido.

iii) Ahora hay que agregar la semántica de la declaración. Para esto tiene que editar msytmts / langlet.py y agregar un visitante de 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 a langlets / mystmts y escriba

python run_mystmts.py

Ahora se iniciará una sesión y se puede usar la nueva declaración definida:

__________________________________________________________________________________

 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

Unos cuantos pasos para llegar a una declaración trivial, ¿verdad? Todavía no hay una API que permita definir cosas simples sin tener que preocuparse por las gramáticas. Pero EE es muy confiable módulo algunos errores. Por lo tanto, es solo cuestión de tiempo que surja una API que permita a los programadores definir cosas convenientes como operadores de infijo o declaraciones pequeñas utilizando solo la programación OO conveniente. Para cosas más complejas como incrustar lenguajes enteros en Python mediante la construcción de un langlet, no hay forma de evitar un enfoque gramatical completo.


fuente
11

Aquí hay una manera muy simple pero horrible de agregar nuevas declaraciones, solo en modo interpretativo . Lo estoy usando para pequeños comandos de 1 letra para editar anotaciones genéticas usando solo sys.displayhook, pero para poder responder a esta pregunta también agregué sys.excepthook para los errores de sintaxis. Este último es realmente feo, recuperando el código sin procesar del búfer de línea de lectura. El beneficio es que es trivialmente fácil agregar nuevas declaraciones de esta manera.


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
fuente
4

He encontrado una guía sobre cómo agregar nuevas declaraciones:

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

Básicamente, para agregar nuevas declaraciones, debe editar Python/ast.c (entre otras cosas) y volver a compilar el binario de Python.

Si bien es posible, no lo hagas. Puede lograr casi todo a través de funciones y clases (que no requerirán que las personas recompilen Python solo para ejecutar su script ...)

dbr
fuente
El enlace real a PDF: esa "conversión automática" está rota y se ha roto por Dios sabe ahora: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX
3

Es posible hacer esto usando EasyExtend :

EasyExtend (EE) es un generador de preprocesador y un marco de metaprogramación escrito en Python puro e integrado con CPython. El propósito principal de EasyExtend es la creación de lenguajes de extensión, es decir, agregar sintaxis y semántica personalizadas a Python.

Matthew Trevor
fuente
1
Siguiendo ese enlace ahora aparece una página: "EasyExtend está muerto. Para aquellos que estén interesados ​​en EE hay un proyecto sucesor llamado Langscape Nombre diferente, rediseño completo, mismo viaje". Dado que existe el peligro de que esta página de información se bloquee, tal vez sea una buena idea actualizar la respuesta.
celtschk
1

No sin modificar el intérprete. Sé que muchos idiomas en los últimos años han sido descritos como "extensibles", pero no en la forma en que lo estás describiendo. Extiende Python agregando funciones y clases.

Bill el lagarto
fuente
1

Hay un lenguaje basado en Python llamado Logix con el que PUEDES hacer tales cosas. No ha estado en desarrollo durante un tiempo, pero las características que solicitó funcionan con la última versión.

Claudiu
fuente
Suena interesante, pero parece haber muerto alrededor de 2009: web.archive.org/web/20090107014050/http://livelogix.net/logix
Tobias Kienzler
1

Algunas cosas se pueden hacer con decoradores. Supongamos, por ejemplo, que Python no tenía ninguna withdeclaración. Entonces podríamos implementar un comportamiento similar como este:

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

Sin embargo, es una solución bastante sucia como se hace aquí. Especialmente el comportamiento donde el decorador llama a la función y se pone _a Nonees inesperado. Para aclarar: este decorador es equivalente a escribir

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

y normalmente se espera que los decoradores modifiquen, no ejecuten, funciones.

Utilicé dicho método antes en un script donde tuve que configurar temporalmente el directorio de trabajo para varias funciones.

kdb
fuente
0

Hace diez años no podías, y dudo que eso haya cambiado. Sin embargo, no fue tan difícil modificar la sintaxis en ese entonces si estaba preparado para recompilar Python, y dudo que eso haya cambiado, tampoco.

Alex Coventry
fuente