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

StackOverflow https://stackoverflow.com/questions/214881

Вопрос

Можете ли вы добавить новые утверждения (например, 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, и я сомневаюсь, что он тоже изменился.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top