Можете ли вы добавить новые инструкции в синтаксис Python?

Можете ли вы добавить новые инструкции (например, print, raise, with) в синтаксис Python?

Скажите, чтобы разрешить..

mystatement "Something"

Или

new_if True:
    print "example"

Не так много, если нужно, а скорее, если это возможно (не доработав код интерпретатора питона)

Ответ 1

Вы можете найти это полезным - Внутренние элементы 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 и создаете 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]. Это демонстрирует общую технику, которую я использую при изменении исходного кода. Im не знакомы с: работой по подобию. Этот принцип не будет решать все ваши проблемы, но он может определенно облегчить процесс. Поскольку все, что нужно сделать для while, также необходимо сделать для until, оно служит довольно хорошим ориентиром.

Обратите внимание, что я решил исключить предложение else из моего определения until, просто чтобы сделать его немного другим (и потому, что я не люблю предложение else циклов и не думаю он хорошо вписывается в Zen of Python).

Второе изменение - изменить правило для compound_stmt, чтобы включить until_stmt, как вы можете видеть в приведенном выше фрагменте. Это сразу после while_stmt, снова.

Когда вы запустите make после изменения Grammar/Grammar, обратите внимание, что программа pgen запускается для повторного сгенерирования Include/graminit.h и Python/graminit.c, а затем несколько файлов перекомпилируются.

Изменение кода генерации AST

После того, как парсер Python создал дерево разбора, это дерево преобразуется в AST, поскольку ASTs гораздо проще работать с в последующих этапы процесса компиляции.

Итак, мы собираемся посетить Parser/Python.asdl, который определяет структуру АСТ Python и добавляет AST node для нашего нового оператора 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 script, это своего рода bootstrapping - для создания Python с нуля, Python уже должен быть доступен.

Пока Parser/asdl_c.py сгенерировал код для управления нашим новым AST node (в файлах Include/Python-ast.h и Python/Python-ast.c), нам все равно придется написать код, который преобразует соответствующее дерево синтаксического анализа node в это вручную. Это делается в файле 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. Наконец, возвращается новый node с именем until.

Обратите внимание, что мы обращаемся к дереву синтаксического анализа node n, используя некоторые макросы, такие как NCH и CHILD. Это стоит понять - их код находится в Include/node.h.

Отступление: композиция AST

Я решил создать новый тип АСТ для оператора until, но на самом деле это необязательно. Я мог бы сэкономить некоторую работу и реализовать новую функциональность, используя состав существующих узлов AST, поскольку:

until condition:
   # do stuff

Функционально эквивалентно:

while not condition:
  # do stuff

Вместо создания until node в ast_for_until_stmt я мог бы создать Not node с while node в качестве дочернего. Поскольку компилятор AST уже знает, как обрабатывать эти узлы, следующие шаги процесса могут быть пропущены.

Компиляция АСТ в байт-код

Следующим шагом является компиляция AST в байт-код Python. Компиляция имеет промежуточный результат, который является CFG (Control Flow Graph), но поскольку тот же самый код обрабатывает его, я сейчас проигнорирую эту деталь и оставьте ее для другой статьи.

Код, который мы будем смотреть дальше, 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. Как и в остальной части статьи, это было сделано при подражании функции kin 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), который выполняет АСТ так же, как функции генерации кода. Наличие таблицы символов для каждой области помогает компилятору определить некоторую ключевую информацию, например, какие переменные являются глобальными и локальными для области.

Чтобы устранить проблему, мы должны изменить функцию 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 не обрабатывается в инструкции switch symtable_visit_stmt и жалуется. Его всегда важно проверить на наличие предупреждений компилятора!

И теперь мы действительно закончили. Компиляция источника после этого изменения делает выполнение myfoo(3) работать как ожидалось.

Заключение

В этой статье я продемонстрировал, как добавить новый оператор в Python. Несмотря на то, что в коде компилятора Python требуется довольно много возиться, это изменение было непросто реализовать, потому что я использовал аналогичный и существующий оператор в качестве ориентира.

Компилятор Python - сложный кусок программного обеспечения, и я не претендую на то, чтобы быть экспертом в нем. Тем не менее, меня действительно интересуют внутренности Python и, в частности, его интерфейс. Поэтому я нашел это упражнение очень полезным компаньоном для теоретического изучения принципов компилятора и исходного кода. Он послужит основой для будущих статей, которые будут углубляться в компилятор.

Ссылки

Я использовал несколько отличных ссылок для построения этой статьи. Здесь они не имеют особого порядка:

  • PEP 339: Дизайн компилятора CPython - вероятно, самая важная и всеобъемлющая часть официальной документации для компилятора Python. Будучи очень коротким, он мучительно отображает нехватку хорошей документации о внутренних компонентах Python.
  • "Внутренние компоненты компилятора Python" - статья Томаса Ли
  • "Python: дизайн и реализация" - презентация Гвидо ван Россума
  • Python (2.5) Виртуальная машина, экскурсия - презентация Питера Трегера

исходный источник

Ответ 2

Один из способов сделать это - предварительно обработать источник и изменить его, переведя добавленный оператор в 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"

Другим довольно аккуратным (хотя и хаккой) решением является создание пользовательской кодировки (см. PEP 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) любой код, начинающийся с комментария "# code: 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 видит это предварительно обработанный файл, что означает, что текст, напечатанный в трассировке стека и т.д., Будет ссылаться на это. Если вы выполнили значительный перевод, это может сильно отличаться от исходного текста. Приведенный выше пример не меняет номера строк и т.д., Поэтому не будет слишком отличаться, но чем больше вы его измените, тем сложнее будет выяснить.

Ответ 3

Да, в некоторой степени это возможно. Существует модуль, который использует 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"

Ответ 4

За исключением изменения и перекомпиляции исходного кода (что возможно с открытым исходным кодом), изменение базового языка на самом деле невозможно.

Даже если вы перекомпилируете исходный код, это не будет python, просто ваша взломанная измененная версия, в которой вам нужно быть очень осторожным, чтобы не вводить ошибки.

Однако я не уверен, зачем вам это нужно. Объектно-ориентированные функции Python упрощают достижение аналогичных результатов с использованием языка в его нынешнем виде.

Ответ 5

Общий ответ: вам нужно предварительно обработать исходные файлы.

Более конкретный ответ: установите EasyExtend и выполните следующие шаги

i) Создайте новый langlet (язык расширения)

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 node.

 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 to 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 появляется, что позволяет программистам определять удобные вещи, такие как операторы infix или небольшие инструкции, используя только удобное программирование OO. Для более сложных вещей, таких как встраивание целых языков в Python с помощью построения langlet, нет способа обойти полный подход к грамматике.

Ответ 6

Здесь очень простой, но дерьмовый способ добавления новых операторов только в режиме интерпретации. Я использую его для небольших 1-буквенных команд для редактирования аннотаций генов, используя только sys.displayhook, но просто чтобы ответить на этот вопрос, я добавил sys.excepthook для синтаксических ошибок. Последний действительно уродлив, извлекает необработанный код из буфера чтения. Преимущество в том, что тривиально легко добавить новые заявления таким образом.


[email protected]:~/$ 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

Ответ 7

Я нашел руководство по добавлению новых операторов, преобразованных из PDF в HTML с помощью Google:

http://209.85.173.104/search?q=cache:IjUb82taSq0J:www.troeger.eu/teaching/pythonvm08lab.pdf+python+add+statement&hl=en&ct=clnk&cd=10

В принципе, чтобы добавить новые инструкции, вы должны отредактировать Python/ast.c (между прочим) и перекомпилировать двоичный код python.

Пока это возможно, не делайте этого. Вы можете добиться почти всего, используя функции и классы (которые не требуют, чтобы люди перекомпилировали python только для запуска вашего script..)

Ответ 8

Это можно сделать с помощью EasyExtend:

EasyExtend (EE) является препроцессором генератор и метапрограммирование рамки, написанные на чистом Python и интегрированный с CPython. Главный Цель EasyExtend - создание языков расширения, то есть добавление настраиваемый синтаксис и семантика для Python.

Ответ 9

Не изменяя интерпретатора. Я знаю, что многие языки за последние несколько лет были описаны как "расширяемые", но не так, как вы описываете. Вы расширяете Python, добавляя функции и классы.

Ответ 10

Существует язык, основанный на python под названием Logix, с которым вы МОЖЕТЕ делать такие вещи. Он не разрабатывался какое-то время, но функции, которые вы просили , работают с последней версией.

Ответ 11

Некоторые вещи можно сделать с декораторами. Пусть, например, предположим, что у 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.

и декораторы обычно ожидают изменения, а не выполнения.

Я использовал такой метод раньше в script, где мне пришлось временно установить рабочий каталог для нескольких функций.

Ответ 12

Это не совсем то, что добавляет новые инструкции в синтаксис языка, но макросы - это мощный инструмент: https://github.com/lihaoyi/macropy

Ответ 13

Десять лет назад вы не могли, и я сомневаюсь, что это изменилось. Однако было сложно изменить синтаксис тогда, если вы были готовы перекомпилировать python, и я сомневаюсь, что это тоже изменилось.