Можете ли вы добавить новые операторы в синтаксис Python?
-
03-07-2019 - |
Вопрос
Можете ли вы добавить новые утверждения (например, print
, raise
, with
) к синтаксису Python?
Дескать, разрешить..
mystatement "Something"
Или,
new_if True:
print "example"
Не так уж и много, если ты должен, а скорее, если это возможно (если не считать изменения кода интерпретатора Python)
Решение
Вы можете найти это полезным - Внутреннее устройство Python:добавление нового оператора в Python, цитируем здесь:
Эта статья — попытка лучше понять, как работает интерфейс Python.Просто читать документацию и исходный код может быть немного скучно, поэтому здесь я применю практический подход:Я собираюсь добавить until
заявление для Python.
Весь код для этой статьи был написан с использованием передовой ветки Py3k в Зеркало репозитория Python Mercurial.
А until
заявление
Некоторые языки, например Ruby, имеют until
заявление, которое является дополнением к while
(until num == 0
эквивалентно while num != 0
).В Ruby я могу написать:
num = 3
until num == 0 do
puts num
num -= 1
end
И он напечатает:
3
2
1
Итак, я хочу добавить аналогичную возможность в Python.То есть иметь возможность писать:
num = 3
until num == 0:
print(num)
num -= 1
Языковое отступление
Эта статья не пытается предложить добавление until
заявление для Python.Хотя я думаю, что такое утверждение сделало бы некоторый код более понятным, и эта статья показывает, насколько легко его добавить, я полностью уважаю философию минимализма Python.На самом деле все, что я здесь пытаюсь сделать, — это получить некоторое представление о внутренней работе Python.
Изменение грамматики
Python использует собственный генератор синтаксического анализатора с именем pgen
.Это парсер LL(1), который преобразует исходный код Python в дерево разбора.Входными данными для генератора парсера является файл Grammar/Grammar
[1].Это простой текстовый файл, определяющий грамматику Python.
[1]:С этого момента ссылки на файлы в исходном коде Python даются относительно корня дерева исходного кода, который представляет собой каталог, в котором вы запускаете команду configure и make для сборки Python.
В файл грамматики необходимо внести две модификации.Во-первых, необходимо добавить определение until
заявление.Я нашел, где while
утверждение было определено (while_stmt
), и добавил until_stmt
ниже [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]:Это демонстрирует распространенный метод, который я использую при изменении исходного кода, с которым я не знаком: работать по сходству.Этот принцип не решит всех ваших проблем, но определенно может облегчить процесс.Поскольку все, что нужно сделать для while
также необходимо сделать для until
, это служит довольно хорошим ориентиром.
Обратите внимание, что я решил исключить else
пункт из моего определения until
, просто чтобы сделать его немного другим (и поскольку, честно говоря, мне не нравится else
предложение циклов и не думаю, что оно хорошо вписывается в дзен Python).
Второе изменение заключается в изменении правила для compound_stmt
включать until_stmt
, как вы можете видеть во фрагменте выше.Это сразу после while_stmt
, снова.
Когда ты бежишь make
после изменения Grammar/Grammar
, обратите внимание, что pgen
программа запускается для повторной генерации Include/graminit.h
и Python/graminit.c
, а затем несколько файлов перекомпилируются.
Изменение кода генерации AST
После того, как парсер Python создал дерево разбора, это дерево преобразуется в AST, поскольку AST гораздо проще работать с на последующих этапах процесса компиляции.
Итак, мы собираемся в гости Parser/Python.asdl
который определяет структуру AST Python и добавляет узел AST для нашего нового until
заявление, опять же прямо под while
:
| While(expr test, stmt* body, stmt* orelse)
| Until(expr test, stmt* body)
Если вы сейчас запустите make
, обратите внимание, что прежде чем компилировать кучу файлов, Parser/asdl_c.py
запускается для генерации кода C из файла определения AST.Это похоже на Grammar/Grammar
) — еще один пример исходного кода Python, использующего мини-язык (другими словами, DSL) для упрощения программирования.Также обратите внимание, что поскольку Parser/asdl_c.py
это скрипт Python, это своего рода самонастройка — чтобы собрать Python с нуля, Python уже должен быть доступен.
Пока Parser/asdl_c.py
сгенерировал код для управления нашим новым узлом AST (в файлы Include/Python-ast.h
и Python/Python-ast.c
), нам все равно придется написать код, который вручную преобразует в него соответствующий узел дерева разбора.Это делается в файле Python/ast.c
.Там функция с именем ast_for_stmt
преобразует узлы дерева разбора операторов в узлы AST.И снова под руководством нашего старого друга while
, мы прыгаем прямо в большое switch
для обработки составных операторов и добавьте предложение для until_stmt
:
case while_stmt:
return ast_for_while_stmt(c, ch);
case until_stmt:
return ast_for_until_stmt(c, ch);
Теперь нам следует реализовать ast_for_until_stmt
.Вот:
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;
}
Опять же, это было закодировано при внимательном рассмотрении эквивалента ast_for_while_stmt
, с той разницей, что для until
Я решил не поддерживать else
пункт.Как и ожидалось, AST создается рекурсивно с использованием других функций создания AST, таких как ast_for_expr
для выражения условия и ast_for_suite
для тела until
заявление.Наконец, новый узел с именем Until
возвращается.
Обратите внимание, что мы обращаемся к узлу дерева разбора n
используя некоторые макросы, такие как NCH
и CHILD
.Это стоит понять — их код находится в Include/node.h
.
Отступление:Состав АСТ
Я решил создать новый тип AST для until
заявление, но на самом деле в этом нет необходимости.Я мог бы сэкономить немного времени и реализовать новую функциональность, используя композицию существующих узлов AST, поскольку:
until condition:
# do stuff
Функционально эквивалентен:
while not condition:
# do stuff
Вместо того, чтобы создавать Until
узел в ast_for_until_stmt
, я мог бы создать Not
узел с While
узел как дочерний.Поскольку компилятор AST уже знает, как обрабатывать эти узлы, следующие шаги процесса можно пропустить.
Компиляция AST в байт-код
Следующим шагом является компиляция AST в байт-код Python.Компиляция дает промежуточный результат — CFG (график потока управления), но поскольку его обрабатывает тот же код, я пока проигнорирую эту деталь и оставлю ее для другой статьи.
Код, который мы рассмотрим далее: Python/compile.c
.Следуя примеру while
, находим функцию compiler_visit_stmt
, который отвечает за компиляцию операторов в байт-код.Мы добавляем пункт для Until
:
case While_kind:
return compiler_while(c, s);
case Until_kind:
return compiler_until(c, s);
Если вам интересно, что Until_kind
то есть это константа (фактически значение _stmt_kind
перечисление), автоматически создаваемое из файла определения AST в Include/Python-ast.h
.В любом случае, мы позвоним compiler_until
которого, конечно, до сих пор не существует.Я вернусь к этому через минуту.
Если вам так же любопытно, как и мне, вы заметите, что compiler_visit_stmt
является своеобразным.Никакого количества grep
-ping исходного дерева показывает, где оно вызывается.В этом случае остается только один вариант — C macro-fu.Действительно, короткое расследование приводит нас к VISIT
макрос, определенный в Python/compile.c
:
#define VISIT(C, TYPE, V) {\
if (!compiler_visit_ ## TYPE((C), (V))) \
return 0; \
Он используется для вызова compiler_visit_stmt
в compiler_body
.Однако вернемся к нашим делам...
Как и обещал, вот 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;
}
Я хотел бы в кое-чем признаться:этот код был написан без глубокого понимания байт-кода Python.Как и остальная статья, сделана в подражание кину compiler_while
функция.Однако, внимательно прочитав его, помня, что виртуальная машина Python основана на стеке, и заглянув в документацию по dis
модуль, который имеет список байт-кодов Python с описаниями можно понять, что происходит.
Всё, мы закончили...Не так ли?
После внесения всех изменений и запуска make
, мы можем запустить только что скомпилированный Python и попробовать наш новый until
заявление:
>>> until num == 0:
... print(num)
... num -= 1
...
3
2
1
Вуаля, это работает!Давайте посмотрим байт-код, созданный для нового оператора, с помощью dis
модуль следующим образом:
import dis
def myfoo(num):
until num == 0:
print(num)
num -= 1
dis.dis(myfoo)
Вот результат:
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
Самая интересная операция — номер 12:если условие истинно, мы переходим после цикла.Это правильная семантика для until
.Если переход не выполняется, тело цикла продолжает выполняться до тех пор, пока не вернется к состоянию операции 35.
Почувствовав удовлетворение от своих изменений, я попытался запустить функцию (выполнив myfoo(3)
) вместо отображения его байт-кода.Результат оказался менее чем обнадеживающим:
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'
Ого...это не может быть хорошо.Так что же пошло не так?
Случай с отсутствующей таблицей символов
Одним из шагов, которые компилятор Python выполняет при компиляции AST, является создание таблицы символов для компилируемого кода.Звонок в PySymtable_Build
в PyAST_Compile
вызывает модуль таблицы символов (Python/symtable.c
), который проходит через AST аналогично функциям генерации кода.Наличие таблицы символов для каждой области помогает компилятору определить некоторую ключевую информацию, например, какие переменные являются глобальными, а какие локальными для области.
Чтобы решить проблему, нам нужно изменить symtable_visit_stmt
функционировать в Python/symtable.c
, добавив код для обработки until
операторы, после аналогичного кода для while
заявления [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]:Кстати, без этого кода компилятор выдает предупреждение Python/symtable.c
.Компилятор замечает, что Until_kind
значение перечисления не обрабатывается в операторе переключения symtable_visit_stmt
и жалуется.Всегда важно проверять предупреждения компилятора!
И теперь мы действительно закончили.Компиляция исходного кода после этого изменения приводит к выполнению myfoo(3)
работать как положено.
Заключение
В этой статье я продемонстрировал, как добавить новый оператор в Python.Хотя это изменение потребовало немалой доработки в коде компилятора Python, реализовать его было несложно, поскольку в качестве руководства я использовал аналогичный и уже существующий оператор.
Компилятор Python — это сложная программа, и я не претендую на то, чтобы быть экспертом в ней.Однако меня действительно интересует внутреннее устройство Python, и особенно его интерфейсная часть.Поэтому я нашел это упражнение очень полезным дополнением к теоретическому изучению принципов компилятора и исходного кода.Он послужит основой для будущих статей, которые будут более подробно изучать компилятор.
Рекомендации
Для написания этой статьи я использовал несколько отличных ссылок.Вот они, в произвольном порядке:
- ПЭП 339:Проектирование компилятора CPython - вероятно, самая важная и всеобъемлющая часть чиновник документация для компилятора Python.Будучи очень коротким, он болезненно демонстрирует нехватку хорошей документации по внутреннему устройству Python.
- «Внутреннее устройство компилятора Python» — статья Томаса Ли
- «Питон:Проектирование и реализация» - презентация Гвидо ван Россума
- Виртуальная машина Python (2.5), Экскурсия - презентация Питера Трёгера
Другие советы
Один из способов сделать это — предварительно обработать исходный код и изменить его, переведя добавленный оператор в Python.Этот подход может вызвать различные проблемы, и я бы не рекомендовал его для общего использования, но для экспериментов с языком или специального метапрограммирования иногда он может быть полезен.
Например, предположим, что мы хотим ввести оператор «myprint», который вместо печати на экране записывает данные в определенный файл.то есть:
myprint "This gets logged to file"
было бы эквивалентно
print >>open('/tmp/logfile.txt','a'), "This gets logged to file"
Существуют различные варианты выполнения замены: от замены регулярных выражений до создания AST и написания собственного синтаксического анализатора, в зависимости от того, насколько близко ваш синтаксис соответствует существующему Python.Хороший промежуточный подход — использовать модуль токенизатора.Это должно позволить вам добавлять новые ключевые слова, структуры управления и т. д., интерпретируя исходный код аналогично интерпретатору Python, что позволяет избежать поломок, которые могут вызвать грубые решения регулярных выражений.Для приведенного выше «myprint» вы можете написать следующий код преобразования:
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
(Это действительно делает myprint ключевым словом, поэтому использование его в качестве переменной в другом месте, скорее всего, вызовет проблемы)
Тогда проблема в том, как его использовать, чтобы ваш код можно было использовать из Python.Один из способов — написать собственную функцию импорта и использовать ее для загрузки кода, написанного на вашем собственном языке.то есть:
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
Однако для этого необходимо, чтобы вы обрабатывали свой индивидуальный код иначе, чем обычные модули Python.то есть "some_mod = myimport("some_mod.py")
" скорее, чем "import some_mod
"
Еще одно довольно аккуратное (хотя и хакерское) решение — создать собственную кодировку (см. ПЭП 263) как этот рецепт демонстрирует.Вы можете реализовать это как:
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)
Теперь после запуска этого кода (например.вы можете поместить его в свой .pythonrc или site.py) любой код, начинающийся с комментария «# coding:mylang» будет автоматически переведен на вышеуказанном этапе предварительной обработки.например.
# 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")
Предостережения:
В подходе с препроцессором есть проблемы, о которых вы, вероятно, знаете, если работали с препроцессором C.Основной из них — отладка.Все, что видит Python, — это предварительно обработанный файл, а это означает, что текст, напечатанный в трассировке стека и т. д., будет ссылаться на него.Если вы выполнили значительный перевод, он может сильно отличаться от исходного текста.В приведенном выше примере не изменяются номера строк и т. д., поэтому он не будет сильно отличаться, но чем больше вы его меняете, тем сложнее будет разобраться.
Да, в некоторой степени это возможно.Eсть модуль там, где используется sys.settrace()
реализовать goto
и comefrom
«ключевые слова»:
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"
Если не считать изменения и перекомпиляции исходного кода (который является возможно с открытым исходным кодом), изменить базовый язык на самом деле невозможно.
Даже если вы перекомпилируете исходный код, это будет не Python, а просто ваша взломанная измененная версия, в которую нужно быть очень осторожным, чтобы не внести в нее ошибки.
Однако я не уверен, почему вам это нужно.Объектно-ориентированные возможности Python позволяют довольно просто добиться аналогичных результатов с помощью языка в его нынешнем виде.
Общий ответ:вам необходимо предварительно обработать исходные файлы.
Более конкретный ответ:установить EasyExtend, и выполните следующие шаги
i) Создайте новый ланглет (язык расширения)
import EasyExtend
EasyExtend.new_langlet("mystmts", prompt = "my> ", source_ext = "mypy")
Без дополнительной спецификации необходимо создать группу файлов в EasyExtend/langlets/mystmts/ .
ii) Откройте mystmts/parsedef/Grammar.ext и добавьте следующие строки
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
Этого достаточно, чтобы определить синтаксис вашего нового оператора.Нетерминал small_stmt является частью грамматики Python и местом, куда подключается новый оператор.Теперь анализатор распознает новый оператор, т.е.исходный файл, содержащий его, будет проанализирован.Однако компилятор отклонит его, поскольку его еще нужно преобразовать в действительный Python.
iii) Теперь нужно добавить семантику утверждения.Ибо это должно отредактировать msytmts/langlet.py и добавить посетителя узла 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) перейдите к langlets/mystmts и введите
python run_mystmts.py
Теперь должен быть запущен сеанс и можно использовать вновь определенный оператор:
__________________________________________________________________________________
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
Довольно много шагов, чтобы прийти к тривиальному утверждению, не так ли?Пока не существует API, позволяющего определять простые вещи, не заботясь о грамматике.Но EE очень надежен с учетом некоторых ошибок.Так что появление API, который позволит программистам определять удобные вещи, такие как инфиксные операторы или небольшие операторы, используя просто удобное объектно-ориентированное программирование, является лишь вопросом времени.Для более сложных задач, таких как встраивание целых языков в Python посредством создания ланглета, невозможно обойтись без полного грамматического подхода.
Вот очень простой, но дрянной способ добавить новые утверждения: только в режиме интерпретации.Я использую его для небольших однобуквенных команд для редактирования аннотаций генов, используя только sys.displayhook, но именно для того, чтобы ответить на этот вопрос, я также добавил sys.Exceptionhook для синтаксических ошибок.Последний вариант действительно уродлив: он извлекает необработанный код из буфера строки чтения.Преимущество в том, что таким образом очень легко добавлять новые операторы.
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
Я нашел руководство по добавлению новых утверждений:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
По сути, чтобы добавить новые операторы, вы должны отредактировать Python/ast.c
(среди прочего) и перекомпилируйте двоичный файл Python.
Хотя это возможно, не делайте этого.Вы можете добиться практически всего с помощью функций и классов (которые не потребуют от людей перекомпиляции Python только для запуска вашего скрипта...)
Это можно сделать, используя EasyExtend:
EasyExtend (EE) - это препроцессорной генератор и структуру метапрограммы, написанная на Pure Python, и интегрированная с CPYTHON.Основной целью EasyExtend является создание языков расширения, т.е.Добавление индивидуального синтаксиса и семантики в Python.
Не без модификации интерпретатора.Я знаю, что за последние несколько лет многие языки описывались как «расширяемые», но не так, как вы описываете.Вы расширяете Python, добавляя функции и классы.
Существует язык, основанный на Python, который называется Логикс с помощью которого вы МОЖЕТЕ делать такие вещи.Некоторое время он не находился в разработке, но функции, которые вы просили, Выполнять работу с последней версией.
Некоторые вещи можно сделать с помощью декораторов.Давайте, например.предположим, что у Python не было with
заявление.Затем мы могли бы реализовать аналогичное поведение следующим образом:
# ====== 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())
Однако это довольно нечистое решение, как здесь.Особенно поведение, когда декоратор вызывает функцию и устанавливает _
к None
является неожиданным.В целях разъяснения:Этот декоратор эквивалентен написанию
def _(infile): ...
_ = mywith(open(...))(_) # mywith returns None.
и декораторы обычно должны модифицировать, а не выполнять функции.
Раньше я использовал такой метод в скрипте, где мне приходилось временно устанавливать рабочий каталог для нескольких функций.
Это не совсем добавление новых операторов в синтаксис языка, но макросы являются мощным инструментом: https://github.com/lihaoyi/macropy
Десять лет назад вы не могли этого сделать, и я сомневаюсь, что что-то изменилось.Однако тогда было не так уж сложно изменить синтаксис, если вы были готовы перекомпилировать Python, и я сомневаюсь, что он тоже изменился.