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)
Respostas:
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
until
declaraçã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
until
declaraçãoAlguns idiomas, como Ruby, têm uma
until
declaração, que é o complemento dewhile
(until num == 0
é equivalente awhile num != 0
). Em Ruby, eu posso escrever:E ele imprimirá:
Então, eu quero adicionar um recurso semelhante ao Python. Ou seja, ser capaz de escrever:
Uma digressão de defesa da linguagem
Este artigo não tenta sugerir a adição de uma
until
declaraçã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 arquivoGrammar/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
until
declaração. Encontrei onde awhile
declaração foi definida (while_stmt
) e adicioneiuntil_stmt
abaixo [2] :[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
while
também precisa ser feitountil
, serve como uma boa orientação.Observe que eu decidi excluir a
else
cláusula da minha definição deuntil
, apenas para torná-la um pouco diferente (e porque francamente eu não gosto daelse
clá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_stmt
incluiruntil_stmt
, como você pode ver no snippet acima. É logo depoiswhile_stmt
, novamente.Quando você executa
make
após a modificaçãoGrammar/Grammar
, observe que opgen
programa é executado para gerar novamenteInclude/graminit.h
ePython/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.asdl
que define a estrutura dos ASTs do Python e adicionar um nó AST para nossa novauntil
declaração, novamente logo abaixo dowhile
: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. EsteGrammar/Grammar
exemplo é 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, comoParser/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.py
geramos o código para gerenciar nosso nó AST recém-definido (nos arquivosInclude/Python-ast.h
ePython/Python-ast.c
), ainda precisamos escrever o código que converte um nó relevante da árvore de análise nele manualmente. Isto é feito no arquivoPython/ast.c
. Lá, uma função denominadaast_for_stmt
converte nós da árvore de análise para instruções em nós AST. Novamente, guiados por nosso velho amigowhile
, saltamos direto para o grande pontoswitch
de manipulação de declarações compostas e adicionamos uma cláusula parauntil_stmt
:Agora devemos implementar
ast_for_until_stmt
. Aqui está:Mais uma vez, isso foi codificado enquanto observava atentamente o equivalente
ast_for_while_stmt
, com a diferença de queuntil
eu decidi não apoiar aelse
cláusula. Como esperado, o AST é criado recursivamente, usando outras funções de criação do AST, comoast_for_expr
para a expressão da condição east_for_suite
para o corpo dauntil
instrução. Finalmente, um novo nó chamadoUntil
é retornado.Observe que acessamos o nó da árvore de análise
n
usando algumas macros comoNCH
eCHILD
. Vale a pena entender - o código deles está inseridoInclude/node.h
.Digressão: composição AST
Eu escolhi criar um novo tipo de AST para a
until
declaraçã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:É funcionalmente equivalente a:
Em vez de criar o
Until
nóast_for_until_stmt
, eu poderia ter criado umNot
nó com umWhile
nó 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 exemplowhile
, encontramos a funçãocompiler_visit_stmt
, que é responsável pela compilação de instruções no bytecode. Adicionamos uma cláusula paraUntil
:Se você quer saber o que
Until_kind
é, é uma constante (na verdade, um valor da_stmt_kind
enumeração) gerada automaticamente do arquivo de definição AST paraInclude/Python-ast.h
. De qualquer forma, chamamos ocompiler_until
que, é claro, ainda não existe. Vou chegar um momento.Se você é curioso como eu, notará que isso
compiler_visit_stmt
é peculiar. Nenhuma quantidade degrep
-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 àVISIT
macro definida emPython/compile.c
:Ele é usado para invocar
compiler_visit_stmt
emcompiler_body
. De volta aos nossos negócios, no entanto ...Como prometido, aqui está
compiler_until
: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_while
função de parentesco . Ao ler com cuidado, no entanto, lembrando que a VM do Python é baseada em pilha e olhando para a documentação dodis
mó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 novauntil
declaração:Voila, funciona! Vamos ver o bytecode criado para a nova instrução usando o
dis
módulo da seguinte maneira:Aqui está o resultado:
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: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_Build
emPyAST_Compile
chamadas 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_stmt
funçãoPython/symtable.c
, adicionando código para manipulação deuntil
instruções, após o código semelhante parawhile
instruções [3] :[3] : A propósito, sem esse código, há um aviso para o compilador
Python/symtable.c
. O compilador percebe que oUntil_kind
valor da enumeração não é tratado na instrução switchsymtable_visit_stmt
e 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:
fonte original
fonte
until
éisa
/isan
como emif something isa dict:
ouif something isan int:
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:
seria equivalente a
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:
(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:
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:
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.
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.
fonte
myimport
em um módulo que contém simplesmenteprint 1
como é apenas linha de rendimentos de código=1 ... SyntaxError: invalid syntax
b=myimport("b.py")
", e b.py contendo apenas "print 1
". Existe algo mais no erro (rastreamento da pilha etc)?import
usa 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
Sim, até certo ponto é possível. Existe um módulo que usa
sys.settrace()
para implementargoto
ecomefrom
"palavras-chave":fonte
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.
fonte
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)
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
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.
iv) cd para langlets / mystmts e tipo
Agora uma sessão deve ser iniciada e a nova instrução definida pode ser usada:
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.
fonte
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.
fonte
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 ..)
fonte
É possível fazer isso usando o EasyExtend :
fonte
Não é exatamente adicionar novas instruções à sintaxe do idioma, mas as macros são uma ferramenta poderosa: https://github.com/lihaoyi/macropy
fonte
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.
fonte
Existe uma linguagem baseada em python chamada Logix com a qual você PODE fazer essas coisas. Ele não está em desenvolvimento há algum tempo, mas os recursos solicitados funcionam com a versão mais recente.
fonte
Algumas coisas podem ser feitas com decoradores. Vamos assumir, por exemplo, que o Python não tinha nenhuma
with
declaração. Poderíamos então implementar um comportamento semelhante como este:É uma solução bastante impura, no entanto, como feito aqui. Especialmente o comportamento em que o decorador chama a função e define
_
comoNone
é inesperado. Para esclarecimento: Este decorador é equivalente a escrevere 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.
fonte
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.
fonte