Você pode adicionar novas instruções à sintaxe do Python?


124

Você pode adicionar novas declarações (como print, raise, with) para a sintaxe do Python?

Diga, para permitir ..

mystatement "Something"

Ou,

new_if True:
    print "example"

Não tanto se você deveria , mas se é possível (menos que modificar o código dos interpretadores python)


10
Em uma observação um pouco relacionada, um caso de uso em que poderia ser útil criar novas declarações dinamicamente (ao contrário de "estender" seriamente o idioma) é para pessoas que usam o intérprete interativo como uma calculadora ou mesmo um shell do SO . Costumo criar pequenas funções descartáveis ​​em tempo real para fazer algo que vou repetir e, nessas situações, seria bom criar comandos muito abreviados, como macros ou instruções, em vez de digitar os nomes longos com a sintaxe function (). É claro que não é para isso que serve o Py .. mas as pessoas passam muito tempo usando-o de forma interativa.
Quilo

5
@Kilo pode valer a pena olhar para o ipython - ele possui muitos recursos shell'ish, por exemplo, você pode usar comandos regulares "ls" e "cd", conclusão de guias, muitos recursos macro-ish etc.
dbr

Algumas linguagens são requintadamente extensíveis, por exemplo, Forth e Smalltalk, mas seus paradigmas de linguagem são diferentes dos usados ​​também pelo Python. Com essas palavras, quaisquer novas palavras (adiante) ou métodos (Smalltalk) tornam-se parte integrante e indistinguível do idioma para essa instalação. Portanto, cada instalação do Forth ou do Smalltalk se torna uma criação única ao longo do tempo. Também é baseado em RPN. Mas, pensando na linha das DSLs, algo como isso deve ser realizado em Python. Porém, como outros já disseram aqui, por quê?

1
Como alguém fluente em Python e Forth e que implementou vários compiladores Forth nos últimos anos, posso contribuir aqui com algum grau de autoridade. Sem obter acesso bruto ao analisador interno do Python, é completamente impossível. Você pode falsificá-lo através do pré-processamento, como ilustram as respostas (francamente, bastante espertas!) Abaixo, mas não é possível atualizar verdadeiramente a sintaxe e / ou semântica da linguagem em um intérprete quente. Essa é a maldição do Python, bem como sua vantagem sobre as linguagens do tipo Lisp e Forth.
Samuel A. Falvo II

Respostas:


153

Você pode achar isso útil - Internos do Python: adicionando uma nova declaração ao Python , citada aqui:


Este artigo é uma tentativa de entender melhor como funciona o front-end do Python. Apenas ler a documentação e o código-fonte pode ser um pouco chato, por isso estou adotando uma abordagem prática aqui: vou adicionar uma untildeclaração ao Python.

Toda a codificação deste artigo foi feita na ramificação de ponta do Py3k no espelho do repositório do Python Mercurial .

A untildeclaração

Alguns idiomas, como Ruby, têm uma untildeclaração, que é o complemento de while( until num == 0é equivalente a while num != 0). Em Ruby, eu posso escrever:

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

E ele imprimirá:

3
2
1

Então, eu quero adicionar um recurso semelhante ao Python. Ou seja, ser capaz de escrever:

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

Uma digressão de defesa da linguagem

Este artigo não tenta sugerir a adição de uma untildeclaração ao Python. Embora eu ache que essa declaração tornaria algum código mais claro, e este artigo mostra como é fácil adicionar, eu respeito completamente a filosofia do minimalismo do Python. Tudo o que estou tentando fazer aqui, na verdade, é obter algumas informações sobre o funcionamento interno do Python.

Modificando a gramática

O Python usa um gerador de analisador personalizado chamado pgen. Este é um analisador LL (1) que converte o código-fonte Python em uma árvore de análise. A entrada para o gerador do analisador é o arquivo Grammar/Grammar[1] . Este é um arquivo de texto simples que especifica a gramática do Python.

[1] : A partir de agora, as referências aos arquivos na fonte Python são fornecidas relativamente à raiz da árvore de fontes, que é o diretório em que você executa o configure e o faz para criar o Python.

Duas modificações devem ser feitas no arquivo de gramática. O primeiro é adicionar uma definição para a untildeclaração. Encontrei onde a whiledeclaração foi definida ( while_stmt) e adicionei until_stmtabaixo [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] : Isso demonstra uma técnica comum que eu uso ao modificar o código-fonte que não estou familiarizado: trabalho por similaridade . Este princípio não resolverá todos os seus problemas, mas definitivamente pode facilitar o processo. Como tudo o que precisa ser feito whiletambém precisa ser feito until, serve como uma boa orientação.

Observe que eu decidi excluir a elsecláusula da minha definição de until, apenas para torná-la um pouco diferente (e porque francamente eu não gosto da elsecláusula de loops e não acho que ela se encaixa bem no Zen de Python).

A segunda alteração é modificar a regra para compound_stmtincluir until_stmt, como você pode ver no snippet acima. É logo depois while_stmt, novamente.

Quando você executa makeapós a modificação Grammar/Grammar, observe que o pgenprograma é executado para gerar novamente Include/graminit.he Python/graminit.c, em seguida, vários arquivos são recompilados.

Modificando o código de geração AST

Depois que o analisador Python cria uma árvore de análise, essa árvore é convertida em um AST, pois os ASTs são muito mais simples de trabalhar nos estágios subsequentes do processo de compilação.

Então, vamos visitar o Parser/Python.asdlque define a estrutura dos ASTs do Python e adicionar um nó AST para nossa nova untildeclaração, novamente logo abaixo do while:

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

Se você executar agora make, observe que antes de compilar um monte de arquivos, Parser/asdl_c.pyé executado para gerar o código C a partir do arquivo de definição AST. Este Grammar/Grammarexemplo é outro exemplo do código-fonte Python usando uma mini-linguagem (em outras palavras, uma DSL) para simplificar a programação. Observe também que, como Parser/asdl_c.pyé um script Python, esse é um tipo de inicialização - para criar o Python do zero, o Python já deve estar disponível.

Enquanto Parser/asdl_c.pygeramos o código para gerenciar nosso nó AST recém-definido (nos arquivos Include/Python-ast.he Python/Python-ast.c), ainda precisamos escrever o código que converte um nó relevante da árvore de análise nele manualmente. Isto é feito no arquivo Python/ast.c. Lá, uma função denominada ast_for_stmtconverte nós da árvore de análise para instruções em nós AST. Novamente, guiados por nosso velho amigo while, saltamos direto para o grande ponto switchde manipulação de declarações compostas e adicionamos uma 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);

Agora devemos implementar ast_for_until_stmt. Aqui 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;
}

Mais uma vez, isso foi codificado enquanto observava atentamente o equivalente ast_for_while_stmt, com a diferença de que untileu decidi não apoiar a elsecláusula. Como esperado, o AST é criado recursivamente, usando outras funções de criação do AST, como ast_for_exprpara a expressão da condição e ast_for_suitepara o corpo da untilinstrução. Finalmente, um novo nó chamado Untilé retornado.

Observe que acessamos o nó da árvore de análise nusando algumas macros como NCHe CHILD. Vale a pena entender - o código deles está inserido Include/node.h.

Digressão: composição AST

Eu escolhi criar um novo tipo de AST para a untildeclaração, mas na verdade isso não é necessário. Eu poderia ter poupado algum trabalho e implementado a nova funcionalidade usando a composição dos nós AST existentes, pois:

until condition:
   # do stuff

É funcionalmente equivalente a:

while not condition:
  # do stuff

Em vez de criar o Untilast_for_until_stmt, eu poderia ter criado um Notnó com um Whilenó como filho. Como o compilador AST já sabe como lidar com esses nós, as próximas etapas do processo podem ser ignoradas.

Compilando ASTs no bytecode

O próximo passo é compilar o AST no bytecode do Python. A compilação tem um resultado intermediário, que é um CFG (Control Flow Graph), mas como o mesmo código lida com isso, ignorarei esse detalhe por enquanto e o deixarei para outro artigo.

O código que veremos a seguir é Python/compile.c. Seguindo o exemplo while, encontramos a função compiler_visit_stmt, que é responsável pela compilação de instruções no bytecode. Adicionamos uma cláusula para Until:

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

Se você quer saber o que Until_kindé, é uma constante (na verdade, um valor da _stmt_kindenumeração) gerada automaticamente do arquivo de definição AST para Include/Python-ast.h. De qualquer forma, chamamos o compiler_untilque, é claro, ainda não existe. Vou chegar um momento.

Se você é curioso como eu, notará que isso compiler_visit_stmté peculiar. Nenhuma quantidade de grep-ping na árvore de origem revela onde é chamada. Quando este for o caso, resta apenas uma opção - C macro-fu. De fato, uma breve investigação nos leva à VISITmacro definida em Python/compile.c:

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

Ele é usado para invocar compiler_visit_stmtem compiler_body. De volta aos nossos negócios, no entanto ...

Como prometido, aqui 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;
}

Tenho uma confissão a fazer: esse código não foi escrito com base em um profundo entendimento do bytecode do Python. Como o restante do artigo, foi feito imitando a compiler_whilefunção de parentesco . Ao ler com cuidado, no entanto, lembrando que a VM do Python é baseada em pilha e olhando para a documentação do dismódulo, que possui uma lista de bytecodes do Python com descrições, é possível entender o que está acontecendo.

É isso aí, terminamos ... Não estamos?

Depois de fazer todas as alterações e executar make, podemos executar o Python recém-compilado e tentar nossa nova untildeclaração:

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

Voila, funciona! Vamos ver o bytecode criado para a nova instrução usando o dismódulo da seguinte maneira:

import dis

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

dis.dis(myfoo)

Aqui está o 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

A operação mais interessante é o número 12: se a condição for verdadeira, saltaremos para após o loop. Esta é a semântica correta para until. Se o salto não for executado, o corpo do loop continuará em execução até retornar à condição na operação 35.

Sentindo-me bem com minha alteração, tentei executar a função (executando myfoo(3)) em vez de mostrar seu bytecode. O resultado foi menos que encorajador:

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 ... isso não pode ser bom. Então, o que deu errado?

O caso da tabela de símbolos ausente

Uma das etapas que o compilador Python executa ao compilar o AST é criar uma tabela de símbolos para o código que ele compila. A chamada para PySymtable_Buildem PyAST_Compilechamadas para o módulo da tabela de símbolos ( Python/symtable.c), que anda a AST de uma maneira semelhante às funções de geração de código. Ter uma tabela de símbolos para cada escopo ajuda o compilador a descobrir algumas informações importantes, como quais variáveis ​​são globais e quais são locais em um escopo.

Para corrigir o problema, precisamos modificar a symtable_visit_stmtfunção Python/symtable.c, adicionando código para manipulação de untilinstruções, após o código semelhante para whileinstruções [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 propósito, sem esse código, há um aviso para o compilador Python/symtable.c. O compilador percebe que o Until_kindvalor da enumeração não é tratado na instrução switch symtable_visit_stmte reclama. É sempre importante verificar se há avisos do compilador!

E agora realmente terminamos. Compilar a fonte após essa alteração faz a execução do myfoo(3)trabalho conforme o esperado.

Conclusão

Neste artigo, demonstramos como adicionar uma nova declaração ao Python. Embora exigindo um pouco de ajustes no código do compilador Python, a mudança não foi difícil de implementar, porque usei uma declaração semelhante e existente como orientação.

O compilador Python é um pedaço sofisticado de software, e não pretendo ser um especialista nele. No entanto, estou realmente interessado nos elementos internos do Python, e particularmente no seu front-end. Portanto, achei este exercício um companheiro muito útil para o estudo teórico dos princípios e código fonte do compilador. Servirá de base para futuros artigos que se aprofundarão no compilador.

Referências

Eu usei algumas excelentes referências para a construção deste artigo. Aqui eles estão em nenhuma ordem particular:

  • PEP 339: Projeto do compilador CPython - provavelmente a parte mais importante e abrangente da documentação oficial do compilador Python. Por ser muito curto, mostra dolorosamente a escassez de boa documentação dos internos do Python.
  • "Internos do compilador Python" - um artigo de Thomas Lee
  • "Python: Design e Implementação" - uma apresentação de Guido van Rossum
  • Python (2.5) Virtual Machine, Uma visita guiada - uma apresentação por Peter Tröger

fonte original


7
Excelente artigo (/ blog), obrigado! Aceitar, já que isso responde perfeitamente à pergunta, e as respostas "não faça isso" / "coding: mylang" já são altamente votadas, por isso aparecerão muito bem na ordem \ o /
dbr

1
Mas, infelizmente, isso não é uma resposta. O artigo vinculado é, mas você não pode votar ou aceitar. As respostas que consistem inteiramente em apenas um link são desencorajadas.
Alfe13 /

6
@ Alfe: publicado há dois anos, aceito e marcado com +1 por 16 leitores. Observe que ele está vinculado ao meu próprio post no blog e copiar um artigo grande no StackOverflow não é algo que pretendo fazer. Sinta-se à vontade para fazer isso em uma edição útil, em vez de brincar de polícia.
Eli Bendersky

2
@ EliBendersky Útil é um eufemismo para esse artigo. Obrigado por explicar muito sobre como essas coisas realmente funcionam em python. Isso realmente me ajudou a entender o AST, que é relevante para o meu trabalho atual. ** também, no caso de você estiver curioso, a minha versão do untilé isa/ isancomo em if something isa dict:ouif something isan int:
Inversus

5
Soo, esta resposta é "escrever e compilar seu próprio idioma de origem, bifurcada de python"
ThorSummoner

53

Uma maneira de fazer coisas assim é pré-processar a fonte e modificá-la, traduzindo sua declaração adicionada para python. Existem vários problemas que essa abordagem trará, e eu não a recomendaria para uso geral, mas, para experiências com linguagem ou metaprogramação para fins específicos, pode ocasionalmente ser útil.

Por exemplo, digamos que queremos introduzir uma declaração "myprint", que, em vez de imprimir na tela, registra em um arquivo específico. ou seja:

myprint "This gets logged to file"

seria equivalente a

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

Existem várias opções de como fazer a substituição, da substituição do regex à geração de um AST, até a gravação do seu próprio analisador, dependendo de quão perto sua sintaxe corresponde ao python existente. Uma boa abordagem intermediária é usar o módulo tokenizer. Isso deve permitir que você adicione novas palavras-chave, estruturas de controle etc., enquanto interpreta a fonte de maneira semelhante ao interpretador python, evitando assim que as soluções brutas de regex causariam. Para o "myprint" acima, você pode escrever o seguinte código de transformação:

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

(Isso faz do myprint efetivamente uma palavra-chave; portanto, o uso como variável em outros lugares provavelmente causará problemas)

O problema então é como usá-lo para que seu código possa ser usado no python. Uma maneira seria apenas escrever sua própria função de importação e usá-la para carregar o código escrito em seu idioma personalizado. ou seja:

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

Isso requer que você lide com seu código personalizado de maneira diferente dos módulos python normais. ie " some_mod = myimport("some_mod.py")" ao invés de " import some_mod"

Outra solução bastante simples (embora hacky) é criar uma codificação personalizada (consulte o PEP 263 ), como esta receita demonstra. Você pode implementar isso 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)

Agora, após a execução desse código (por exemplo, você pode colocá-lo no seu .pythonrc ou site.py) qualquer código que comece com o comentário "# coding: mylang" será automaticamente traduzido pela etapa de pré-processamento acima. por exemplo.

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

Ressalvas:

Existem problemas na abordagem do pré-processador, como você provavelmente estará familiarizado se tiver trabalhado com o pré-processador C. O principal é a depuração. Tudo o que o python vê é o arquivo pré-processado, o que significa que o texto impresso no rastreamento da pilha etc se refere a isso. Se você realizou uma tradução significativa, isso pode ser muito diferente do seu texto de origem. O exemplo acima não altera os números de linha, etc., portanto, não será muito diferente, mas quanto mais você o alterar, mais difícil será descobrir.


12
Agradável! Em vez de dizer 'não pode ser burro', você realmente dá algumas boas respostas (que se resumem a 'você realmente não quer fazer isso').
C0m4 19/10/08

Eu não estou certo que eu compreendo como o exemplo primeiros trabalhos - tentando usar myimportem um módulo que contém simplesmente print 1como é apenas linha de rendimentos de código=1 ... SyntaxError: invalid syntax
olamundo

@ noam: não tenho certeza do que está falhando para você - aqui eu apenas recebo "1" como esperado. (Isso ocorre com os 2 blocos que começam com "import tokenize" e "import new" acima, coloque o arquivo a.py, bem como " b=myimport("b.py")", e b.py contendo apenas " print 1". Existe algo mais no erro (rastreamento da pilha etc)?
Brian

3
O Python3 parece não permitir isso, embora não necessariamente de propósito; Eu recebo um erro de BOM.
Tobu

observe que importusa o builtin __import__, portanto, se você substituir isso ( antes de importar o módulo que requer a importação modificada), não precisará de um separadormyimport
Tobias Kienzler

21

Sim, até certo ponto é possível. Existe um módulo que usa sys.settrace()para implementar gotoe comefrom"palavras-chave":

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
Mas essa não é uma sintaxe realmente nova ... apenas parece com isso.
Hans Nowak

3
-1: A página vinculada tem este cabeçalho: "O módulo 'goto' foi uma piada do tolo de abril, publicada em 1 de abril de 2004. Sim, funciona, mas é uma piada, no entanto. Por favor, não o use em código real!"
Jim

6
@ Jim pode reconsiderar -1. ele mostra o mecanismo de implementação. coisa agradável para começar.
N611x007

14

Com exceção de alterar e recompilar o código fonte (o que é possível com código aberto), alterar o idioma base não é realmente possível.

Mesmo se você recompilar a fonte, não seria python, apenas sua versão alterada invadida pela qual você precisa ter muito cuidado para não introduzir bugs.

No entanto, não sei por que você desejaria. Os recursos orientados a objetos do Python simplificam bastante a obtenção de resultados semelhantes com a linguagem atual.


2
Eu discordo em um ponto. Se você adicionar novas palavras-chave, acho que ainda seria Python. Se você alterar as palavras-chave existentes, isso será hackeado, como você diz.
Bill the Lizard

9
Se você adicionar novas palavras-chave, seria uma linguagem derivada do Python. Se você alterar palavras-chave, seria uma linguagem incompatível com Python.
tzot 18/10/08

1
Se você adicionar palavras-chave, poderá estar perdendo o objetivo de "sintaxe simples e fácil de aprender" e "bibliotecas extensas". Eu acho que os recursos da linguagem quase sempre são um erro (exemplos incluem COBOL, Perl e PHP).
S.Lott 18/10/08

5
Novas palavras-chave quebrariam o código Python, que as usa como identificadores.
Akaihola

12

Resposta geral: você precisa pré-processar seus arquivos de origem.

Resposta mais específica: instale o EasyExtend e siga as etapas a seguir

i) Crie um novo langlet (idioma de extensão)

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

Sem especificação adicional, um monte de arquivos deve ser criado em EasyExtend / langlets / mystmts /.

ii) Abra mystmts / parsedef / Grammar.ext e adicione as seguintes linhas

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

Isso é suficiente para definir a sintaxe da sua nova instrução. O terminal não small_stmt faz parte da gramática Python e é o local em que a nova instrução está conectada. O analisador agora reconhecerá a nova instrução, ou seja, um arquivo de origem contendo ela será analisado. O compilador o rejeitará, porque ainda precisa ser transformado em Python válido.

iii) Agora é preciso adicionar semântica da declaração. Para isso, é necessário editar o msytmts / langlet.py e adicionar um visitante do nó 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 para langlets / mystmts e tipo

python run_mystmts.py

Agora uma sessão deve ser iniciada e a nova instrução definida pode ser usada:

__________________________________________________________________________________

 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

Alguns passos para chegar a uma afirmação trivial, certo? Ainda não existe uma API que permita definir coisas simples sem a necessidade de se preocupar com gramáticas. Mas o EE é um módulo muito confiável, alguns bugs. Portanto, é apenas uma questão de tempo que surge uma API que permite aos programadores definir coisas convenientes, como operadores de infix ou pequenas instruções, usando apenas a programação OO conveniente. Para coisas mais complexas, como incorporar linguagens inteiras no Python por meio da construção de um langlet, não há como contornar uma abordagem gramatical completa.


11

Aqui está uma maneira muito simples, mas de baixa qualidade, de adicionar novas instruções, apenas no modo interpretativo . Estou usando-o para pequenos comandos de uma letra para editar anotações de genes usando apenas sys.displayhook, mas, para poder responder a essa pergunta, adicionei sys.excepthook também para os erros de sintaxe. O último é realmente feio, buscando o código bruto do buffer da linha de leitura. O benefício é que é fácil adicionar novas declarações dessa maneira.


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

Encontrei um guia para adicionar novas declarações:

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

Basicamente, para adicionar novas instruções, você deve editar Python/ast.c(entre outras coisas) e recompilar o binário python.

Embora seja possível, não. Você pode conseguir quase tudo através de funções e classes (o que não exigirá que as pessoas recompilem o python apenas para executar seu script ..)


O link real para o PDF - que a "conversão automática" está quebrada e está quebrada, já que Deus sabe há muito tempo: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX 16/18

3

É possível fazer isso usando o EasyExtend :

O EasyExtend (EE) é um gerador de pré-processador e estrutura de metaprogramação, escrito em Python puro e integrado ao CPython. O principal objetivo do EasyExtend é a criação de linguagens de extensão, ou seja, adicionar sintaxe e semântica personalizadas ao Python.


1
A seguir, o link agora apresenta uma página: "O EasyExtend está morto. Para aqueles que estão interessados ​​em EE, há um projeto sucessor chamado Langscape Nome diferente, redesenho completo, mesma jornada". Como existe o risco de esta página de informações ficar inoperante, talvez seja uma boa ideia atualizar a resposta.
Celtschk # 28/16


1

Não sem modificar o intérprete. Sei que muitos idiomas nos últimos anos foram descritos como "extensíveis", mas não da maneira que você está descrevendo. Você estende o Python adicionando funções e classes.



1

Algumas coisas podem ser feitas com decoradores. Vamos assumir, por exemplo, que o Python não tinha nenhuma withdeclaração. Poderíamos então implementar um comportamento semelhante 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())

É uma solução bastante impura, no entanto, como feito aqui. Especialmente o comportamento em que o decorador chama a função e define _como Noneé inesperado. Para esclarecimento: Este decorador é equivalente a escrever

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

e decoradores normalmente modificam, não executam, funções.

Eu usei esse método antes em um script em que tive que definir temporariamente o diretório de trabalho para várias funções.


0

Há dez anos, você não podia, e duvido que isso tenha mudado. No entanto, não foi tão difícil modificar a sintaxe na época, se você estava preparado para recompilar o python, e duvido que isso também tenha sido alterado.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.