você pode adicionar novas declarações à sintaxe do Python?
-
03-07-2019 - |
Pergunta
Você pode adicionar novas declarações (como print
, raise
, with
) a sintaxe do Python?
Say, para permitir ..
mystatement "Something"
Ou
new_if True:
print "example"
Não tanto se você deve , mas sim se é possível (curto de modificar o código de intérpretes python)
Solução
Você pode encontrar este útil - internos Python: adicionando uma nova declaração para Python , citado aqui:
Este artigo é uma tentativa de entender melhor como o front-end de Python funciona. Basta ler a documentação e código-fonte pode ser um pouco chato, então eu estou tomando um hands-on abordagem aqui:. Eu estou indo para adicionar uma declaração until
para Python
Toda a codificação para este artigo foi feito contra o corte de ponta Py3k filial na Python Mercurial repositório espelho .
A declaração until
Alguns idiomas, como Ruby, tem uma declaração until
, 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 irá imprimir:
3
2
1
Então, eu quero adicionar uma capacidade semelhante para Python. Ou seja, ser capaz de escrever:
num = 3
until num == 0:
print(num)
num -= 1
A linguagem de defesa digressão
Este artigo não tenta sugerir a adição de uma declaração until
para Python. Embora eu acho que essa declaração um faria 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 eu estou tentando fazer aqui, realmente, é ganhar alguns insights sobre o funcionamento interno do Python.
Modificando a gramática
Python usa um gerador de analisador personalizado nomeado pgen
. Esta é uma LL (1) parser que converte Python código-fonte em uma árvore de análise. A entrada para o gerador de analisador é o Grammar/Grammar
arquivo [1] . Este é um arquivo de texto simples que especifica a gramática do Python.
[1] : A partir daqui, as referências a arquivos na origem Python são dadas relativamente à raiz da árvore de origem, que é o diretório onde você executar configure e fazer para construir Python.
Duas modificações têm de ser feitas para o arquivo de gramática. A primeira é a de adicionar uma definição para a declaração until
. I encontrada em que a instrução while
foi definida (while_stmt
), e until_stmt
abaixo adicionado
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 quando modificar o código fonte que eu não estou familiarizado com: trabalho por semelhança . Este princípio não vai resolver todos os seus problemas, mas pode definitivamente facilitar o processo. Uma vez que tudo que tem que ser feito para while
também tem que ser feito para until
, ele serve como uma boa orientação bonita.
Note que eu já decidiu excluir a cláusula else
de minha definição de until
, apenas para torná-lo um pouco diferente (e porque, francamente eu não gosto da cláusula else
de loops e não acho que ele se encaixa bem com o Zen de Python).
A segunda mudança é a de modificar a regra para compound_stmt
para incluir until_stmt
, como você pode ver no trecho acima. É logo após while_stmt
, novamente.
Quando você executa make
após modificar Grammar/Grammar
, aviso que o programa pgen
é executado para re-gerar Include/graminit.h
e Python/graminit.c
, e, em seguida, vários arquivos são re-compilado.
Modificar o código de geração AST
Depois do analisador Python criou uma árvore de análise, esta árvore é convertido em um AST, desde ASTs são muito mais simples para trabalhar com em fases subsequentes do processo de compilação.
Então, vamos visita Parser/Python.asdl
que define a estrutura de ASTs do Python e adicione um nó AST para o nosso novo generacdeclaração odicetagcode, novamente à direita abaixo do until
:
| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)
Se você agora executar while
, aviso de que antes de compilar um monte de arquivos, make
é executado para gerar o código C a partir do arquivo de definição de AST. Este (como Parser/asdl_c.py
) é 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, desde Grammar/Grammar
é um script Python, este é um tipo de bootstrapping - para construir Python a partir do zero, Python já tem que estar disponível.
Enquanto Parser/asdl_c.py
gerado o código para gerir o nosso nó AST recém-definido (no Parser/asdl_c.py
arquivos e Include/Python-ast.h
), ainda temos que escrever o código que converte um nó parse-árvore relevante para ele com a mão. Isto é feito na Python/Python-ast.c
arquivo. Lá, uma função chamada convertidos Python/ast.c
de análise nós da árvore para as demonstrações em nós AST. Mais uma vez, guiados por nosso velho amigo ast_for_stmt
, nós ir direto para a grande while
para lidar com instruções compostas e adicionar uma cláusula de switch
:
case while_stmt:
return ast_for_while_stmt(c, ch);
case until_stmt:
return ast_for_until_stmt(c, ch);
Agora, devemos implementar 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, este foi codificado enquanto perto olhando para o ast_for_until_stmt
equivalente, com a diferença de que, para ast_for_while_stmt
eu decidi não apoiar a cláusula until
. Como esperado, a AST é criado de forma recursiva, usando outras funções AST criando assim else
para a expressão condição e ast_for_expr
para o corpo da declaração ast_for_suite
. Finalmente, um novo nó chamado until
é retornado.
Note que acessar o parse-árvore de nós Until
usando alguns macros como n
e NCH
. Estes são dignos de entendimento - do código está em CHILD
Digressão: composição AST
Eu escolhi para criar um novo tipo de AST para a instrução Include/node.h
, mas na verdade isso não é necessário. Eu poderia ter salvo algum trabalho e implementada a nova funcionalidade usando composição de nós AST existentes, uma vez que:
until condition:
# do stuff
É funcionalmente equivalente a:
while not condition:
# do stuff
Em vez de criar o nó until
em Until
, eu poderia ter criado um nó ast_for_until_stmt
com um nó Not
como uma criança. Desde o compilador AST já sabe como lidar com esses nós, os próximos passos do processo pode ser ignorado.
Compilando ASTs em bytecode
O próximo passo é compilar o AST em Python bytecode. A compilação tem um resultado intermediário que é um CFG (Controle de Fluxo Graph), mas desde que os mesmos alças de código que vou ignorar esse detalhe para agora e deixá-lo para outro artigo.
O código veremos a seguir é While
. Seguindo o exemplo de Python/compile.c
, encontramos a while
função, que é responsável por compilar as declarações em bytecode. Nós adicionamos uma cláusula de compiler_visit_stmt
:
case While_kind:
return compiler_while(c, s);
case Until_kind:
return compiler_until(c, s);
Se você quer saber o que Until
é, é uma constante (na verdade um valor da enumeração Until_kind
) gerado automaticamente a partir do arquivo de definição de AST em _stmt_kind
. De qualquer forma, nós chamamos Include/Python-ast.h
que, é claro, ainda não existe. Eu vou chegar a ele uma um momento.
Se você está curioso como eu, você vai perceber que compiler_until
é peculiar. Nenhuma quantidade de compiler_visit_stmt
-ping a árvore fonte revela onde é chamado. Quando este for o caso, apenas uma opção permanece - C macro-fu. Na verdade, a uma curta investigação nos leva a macro grep
definidos no VISIT
:
#define VISIT(C, TYPE, V) {\
if (!compiler_visit_ ## TYPE((C), (V))) \
return 0; \
É usado para invocar Python/compile.c
em compiler_visit_stmt
. Voltar para o nosso negócio, no entanto ...
Como prometido, está aqui compiler_body
:
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;
}
Eu tenho uma confissão a fazer: este código não foi escrito com base em uma profunda compreensão do Python bytecode. Como o resto do artigo, que foi feito em imitação da função parentes compiler_until
. Ao lê-lo com cuidado, no entanto, manter em mente que o Python VM é baseada em pilha, e olhando para a documentação do módulo compiler_while
, que tem uma lista de Python bytecodes com descrições, é possível compreender o que está acontecendo.
É isso aí, estamos a fazer ... não estamos?
Depois de fazer todas as alterações e executar dis
, podemos executar o Python recém-compilados e tentar o nosso nova declaração make
:
>>> until num == 0:
... print(num)
... num -= 1
...
3
2
1
Voila, ele funciona! Vamos ver o bytecode criado para a nova instrução usando o módulo until
da seguinte forma:
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, nós saltar para depois do loop. Esta é semântica correta para dis
. Se o salto não é executado, o corpo do laço continua funcionando até que ele salta de volta para a condição em operação 35.
Sentir-se bem sobre a minha mudança, então eu tentei executar o (until
execução) em vez de mostrar o seu bytecode. O resultado foi menor do que animadores:
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'
Uau ... isso não pode ser bom. Então, o que deu errado?
O caso da tabela de símbolos faltando
Uma das etapas executadas pelo compilador Python ao compilar o AST é criar uma tabela de símbolos para o código que compila. A chamada para myfoo(3)
em PySymtable_Build
chama o módulo da tabela de símbolos (PyAST_Compile
), 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 a figura compilador para fora algumas informações-chave, tais como quais as variáveis ??são globais e que são locais para um escopo.
Para corrigir o problema, temos que modificar a função Python/symtable.c
em symtable_visit_stmt
, adicionando o código para lidar com declarações Python/symtable.c
, após o código semelhante para as demonstrações until
[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] : By the way, sem este código há um aviso do compilador para while
. O compilador percebe que o valor Python/symtable.c
enumeração não é tratado na instrução switch de Until_kind
e queixa-se. É sempre importante verificar se há avisos do compilador!
E agora nós realmente são feitos. Compilando a fonte após esta mudança faz com que a execução do trabalho symtable_visit_stmt
como esperado.
Conclusão
Neste artigo eu demonstrei como adicionar uma nova declaração para Python. Embora exigindo um pouco de mexer no código do compilador Python, a mudança não foi difícil de implementar, porque eu usei uma declaração semelhante e existente como uma diretriz.
O compilador Python é um pedaço sofisticado de software, e não tenho a pretensão de ser um especialista nisso. No entanto, eu estou realmente interessado nos internos do Python, e particularmente o seu front-end. Portanto, eu encontrei este exercício um companheiro muito útil para o estudo teórico dos princípios do compilador e código fonte. Ele servirá como base para futuros artigos que irão se aprofundar no compilador.
Referências
Eu usei algumas excelentes referências para a construção deste artigo. Aqui estão eles, em nenhuma ordem particular:
- PEP 339: Projeto do CPython compilador - provavelmente o mais importante e peça abrangente de funcionário documentação para o compilador Python. Sermuito curto, dolorosamente mostra a escassez de boa documentação dos internos do Python.
- "python compilador Internals" - um artigo de Thomas Lee
- "Python: Desenho e Implementação" - uma apresentação por Guido van Rossum
- Python (2.5) Virtual Machine, A visita guiada - uma apresentação por Peter Tröger
Outras dicas
Uma maneira de fazer as coisas como esta é a pré-processar a fonte e modificá-lo, traduzindo o seu comunicado de python. Há vários problemas esta abordagem trará, e eu não recomendo para uso geral, mas para a experimentação com a linguagem, ou metaprogramming de propósito específico, ele pode ocasionalmente ser útil.
Por exemplo, digamos que queremos introduzir uma declaração "myPrint", que em vez de imprimir a tela em vez logs para 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 sobre a forma de fazer a substituição, de substituição regex para gerar um AST, para escrever seu próprio analisador dependendo python quão perto sua sintaxe jogos existente. Uma boa abordagem intermediário é usar o módulo tokenizador. Isso deve permitir que você adicionar novas palavras-chave, estruturas de controle etc ao interpretar a fonte de forma semelhante ao interpretador Python, evitando assim a ruptura soluções regex bruto causaria. Pelo exposto "myPrint", 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
(Isto faz myPrint efetivamente uma palavra-chave, por isso o uso como uma variável em outros lugares provavelmente causará problemas)
O problema, então, é como usá-lo para que seu código é utilizável a partir de python. Uma maneira seria apenas para escrever sua própria função de importação, e usá-lo para carregar 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ê lidar com o seu código personalizado de forma diferente a partir de módulos normais python no entanto. ou seja, "some_mod = myimport("some_mod.py")
" em vez de "import some_mod
"
Outra solução bastante limpo (embora hacky) é criar uma codificação personalizado (veja PEP 263 ) como esta receita demonstra. Você poderia 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 este código é executado (. Por exemplo, você poderia colocá-lo em sua .pythonrc ou site.py) qualquer código que começa com o comentário "codificação #: mylang" será automaticamente traduzido através do acima pré-processamento passo. 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")
Advertências:
Existem problemas para a abordagem de pré-processamento, como você provavelmente vai estar familiarizado com se você já trabalhou com o pré-processador C. A principal delas é a depuração. Todos python vê é o arquivo pré-processado que significa que o texto impresso no rastreamento de pilha etc irá se referir a isso. Se você realizou a tradução significativa, isso pode ser muito diferente do seu texto de origem. O exemplo acima não muda os números de linha etc, então não vai ser muito diferente, mas quanto mais você alterá-la, o mais difícil será para descobrir.
Sim, até certo ponto, isso é possível. Há um módulo lá fora, que usos sys.settrace()
para implementar goto
e 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"
curta de mudar e recompilar o código fonte (que é possível com código aberto), a alteração do idioma base não é realmente possível.
Mesmo se você recompilar a fonte, que não seria python, apenas a sua versão alterada hackeado-up que você precisa ter muito cuidado para não introduzir erros em.
No entanto, eu não sei por que você iria querer. recursos orientados a objetos do Python torna bastante simples de obter resultados semelhantes com a linguagem tal como está.
resposta geral: você precisa para pré-processar seus arquivos de origem.
Mais resposta específica: instalar EasyExtend , e passar por etapas a seguir
i) Criar um novo Langlet (linguagem de extensão)
import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")
Sem especificação adicional um monte de arquivos será criado sob EasyExtend / langlets / mystmts /.
ii) Abrir mystmts / parsedef / Grammar.ext e adicionar 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
Isto é suficiente para definir a sintaxe de sua nova declaração. O small_stmt não-terminal é parte da gramática Python e é o lugar onde a nova declaração é viciado em. O analisador irá agora reconhecer a nova instrução ou seja, um arquivo de origem contendo será analisado. O compilador irá rejeitá-la, porque embora ele ainda tem que ser transformado em Python válido.
iii) Agora é preciso adicionar semântica da declaração. Para isso é preciso editar msytmts / langlet.py e adicionar um my_stmt nó visitante.
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 iniciado e a declaração recém-definido pode ser usado:
__________________________________________________________________________________
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
Muito poucos passos para chegar a uma declaração trivial, certo? Não há uma API ainda que permite que se define coisas simples, sem ter de se preocupar com gramáticas. Mas EE é muito confiável modulo alguns bugs. Então é só uma questão de tempo que uma API emerge que permite que os programadores definem coisas conveniente como operadores infixas ou declarações pequenos usando programação OO apenas conveniente. Para coisas mais complexas, como a incorporação de idiomas inteiros em Python por meio da construção de uma Langlet não há nenhuma maneira de ir em torno de uma abordagem gramatical completo.
Aqui está uma maneira muito simples, mas de baixa qualidade para adicionar novas declarações, no modo interpretativa única . Eu estou usando-o para os pequenos 1 letras comandos para editar anotações de genes utilizando apenas sys.displayhook, mas apenas para que eu pudesse responder a esta pergunta eu adicionei sys.excepthook para os erros de sintaxe também. O último é realmente feio, buscando o código cru do tampão readline. A vantagem é que é tão fácil de adicionar novas declarações desta forma.
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
Eu encontrei um guia sobre a adição de novas informaçõ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 (que não vai exigir que as pessoas python recompilação apenas para executar o script ..)
É possível fazer isso usando EasyExtend :
EasyExtend (EE) é um pré-processador gerador e metaprogramming framework escrito em puro Python e integrado com CPython. O principal propósito de EasyExtend é a criação de linguagens de extensão adicionando ou seja, sintaxe e semântica personalizada para Python.
Não sem modificar o intérprete. Eu conheço um monte de línguas nos últimos anos têm sido descritos como "extensível", mas não da maneira que você está descrevendo. Você estender Python adicionando funções e classes.
Há uma linguagem baseado em python chamado Logix com que você pode fazer essas coisas. Não tem sido em desenvolvimento há algum tempo, mas as características que você pediu fazer o trabalho com a versão mais recente.
Algumas coisas podem ser feitas com decoradores. Vamos por exemplo assumir, Python não tinha comunicado with
. 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, porém, como feito aqui. Especialmente o comportamento onde o decorador chama a função e conjuntos _
para None
é inesperado. Para esclarecimento: Este decorador é equivalente à escrita ??p>
def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.
e decoradores são normalmente esperado para modificar, não executar, funções.
Eu usei esse método antes em um script onde eu tive que definir temporariamente o diretório de trabalho para várias funções.
Não é adicionando exatamente novas declarações para a sintaxe da linguagem, mas macros são uma ferramenta poderosa: https: // github. com / lihaoyi / macropy
anos Dez atrás você não podia, e eu duvido que mudou. No entanto, não foi tão difícil para modificar a sintaxe de volta, então se você estivesse preparado para python recompilação, e duvido que mudou, também.