Вы можете добавлять новые заявления (как 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 я могу написать:И он напечатает:
Итак, я хочу добавить аналогичную возможность в Python. То есть уметь писать:
Экскурсия по языковой защите
В этой статье не делается попытка предложить добавление
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] :[2] : Это демонстрирует общий прием, который я использую при изменении исходного кода, с которым я не знаком: работа по подобию . Этот принцип не решит всех ваших проблем, но определенно может облегчить процесс. Поскольку все, что нужно сделать,
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, поскольку с AST намного проще работать на последующих этапах процесса компиляции.
Итак, мы собираемся посетить страницу,
Parser/Python.asdl
которая определяет структуру AST Python и добавить узел AST для нашего новогоuntil
оператора, снова прямо подwhile
:Если вы сейчас запустите
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
:Теперь надо реализовать
ast_for_until_stmt
. Вот:Опять же, это было написано при внимательном рассмотрении эквивалента
ast_for_while_stmt
, с той разницей, чтоuntil
я решил не поддерживать этоelse
предложение. Как и ожидалось, AST создается рекурсивно с использованием других функций создания AST, такихast_for_expr
как выражение условия иast_for_suite
телоuntil
оператора. Наконец,Until
возвращается новый узел с именем .Обратите внимание, что мы получаем доступ к узлу дерева синтаксического анализа,
n
используя некоторые макросы, такие какNCH
иCHILD
. Их стоит понять - их код находится в форматеInclude/node.h
.Лирическое отступление: состав AST
Я решил создать новый тип AST для
until
оператора, но на самом деле в этом нет необходимости. Я мог бы сэкономить немного времени и реализовать новую функциональность, используя композицию существующих узлов AST, поскольку:Функционально эквивалентен:
Вместо того, чтобы создавать
Until
узел вast_for_until_stmt
, я мог бы создатьNot
узел сWhile
узлом в качестве дочернего. Поскольку компилятор AST уже знает, как обрабатывать эти узлы, следующие шаги процесса можно пропустить.Компиляция AST в байт-код
Следующим шагом является компиляция AST в байт-код Python. Компиляция дает промежуточный результат, который является CFG (Control Flow Graph), но, поскольку он обрабатывается тем же кодом, я пока проигнорирую эту деталь и оставлю ее для другой статьи.
Код, который мы рассмотрим дальше, - это
Python/compile.c
. Следуя примеруwhile
, мы находим функциюcompiler_visit_stmt
, которая отвечает за компиляцию операторов в байт-код. Мы добавляем пункт дляUntil
:Если вам интересно, что
Until_kind
это такое, это константа (на самом деле значение_stmt_kind
перечисления), автоматически генерируемая из файла определения AST вInclude/Python-ast.h
. Во всяком случае, мы называем то,compiler_until
что, конечно, еще не существует. Я займусь этим на мгновение.Если вам любопытно, как и мне, вы заметите, что
compiler_visit_stmt
это странно. Никакоеgrep
нажатие на дерево исходных текстов не показывает, где оно вызвано. В таком случае остается только один вариант - C macro-fu. Действительно, небольшое исследование приводит нас кVISIT
макросу, определенному вPython/compile.c
:Он используется для вызова
compiler_visit_stmt
вcompiler_body
. Но вернемся к нашему делу ...Как и было обещано, вот
compiler_until
:Должен признаться: этот код не был написан на основе глубокого понимания байт-кода Python. Как и остальная часть статьи, это было сделано в имитации
compiler_while
функции родства . Однако, внимательно прочитав его, помня, что виртуальная машина Python основана на стеке, и заглянув в документациюdis
модуля, в которой есть список байт-кодов Python с описаниями, можно понять, что происходит.Вот и все, мы закончили ... Не так ли?
После внесения всех изменений и запуска
make
мы можем запустить только что скомпилированный Python и попробовать наш новыйuntil
оператор:Вуаля, работает! Давайте посмотрим на байт-код, созданный для нового оператора, используя
dis
модуль следующим образом:Вот результат:
Самая интересная операция - номер 12: если условие истинно, мы переходим к завершению цикла. Это правильная семантика для
until
. Если переход не выполняется, тело цикла продолжает работать, пока не вернется к состоянию на этапе 35.Чувствуя себя довольным своим изменением, я затем попытался запустить функцию (выполнение
myfoo(3)
) вместо того, чтобы показывать ее байт-код. Результат был менее чем обнадеживающим:Ого ... это не может быть хорошо. Так что же пошло не так?
Случай с отсутствующей таблицей символов
Одним из шагов, выполняемых компилятором Python при компиляции AST, является создание таблицы символов для кода, который он компилирует. Вызов
PySymtable_Build
inPyAST_Compile
вызывает модуль таблицы символов (Python/symtable.c
), который просматривает AST аналогично функциям генерации кода. Наличие таблицы символов для каждой области помогает компилятору выяснить некоторую ключевую информацию, например, какие переменные являются глобальными, а какие - локальными по отношению к области.Чтобы решить эту проблему, мы должны изменить
symtable_visit_stmt
функцию вPython/symtable.c
, добавив код для обработкиuntil
операторов после аналогичного кода дляwhile
операторов [3] :[3] : Кстати, без этого кода компилятор выдает предупреждение для
Python/symtable.c
. Компилятор замечает, чтоUntil_kind
значение перечисления не обрабатывается в операторе switch,symtable_visit_stmt
и жалуется. Всегда важно проверять предупреждения компилятора!И теперь мы действительно закончили. При компиляции исходного кода после этого изменения работа выполняется
myfoo(3)
должным образом.Вывод
В этой статье я продемонстрировал, как добавить новый оператор в Python. Несмотря на то, что потребовалось немного поработать в коде компилятора Python, это изменение было несложно реализовать, потому что я использовал аналогичный существующий оператор в качестве руководства.
Компилятор Python - это сложная часть программного обеспечения, и я не утверждаю, что я в ней эксперт. Однако меня действительно интересует внутреннее устройство Python, и особенно его интерфейс. Поэтому я нашел это упражнение очень полезным дополнением к теоретическому изучению принципов компилятора и исходного кода. Он послужит основой для будущих статей, которые углубятся в компилятор.
Ссылки
Для построения этой статьи я использовал несколько отличных ссылок. Вот они, в произвольном порядке:
первоисточник
источник
until
isisa
/isan
as inif something isa dict:
orif something isan int:
Один из способов сделать это - предварительно обработать исходный код и изменить его, переведя ваш добавленный оператор в python. Этот подход создает различные проблемы, и я бы не рекомендовал его для общего использования, но для экспериментов с языком или специального метапрограммирования он иногда может быть полезен.
Например, допустим, мы хотим ввести оператор «myprint», который вместо вывода на экран ведет журнал в конкретном файле. то есть:
будет эквивалентно
Существуют различные варианты того, как выполнить замену, от подстановки регулярного выражения до генерации AST, до написания собственного парсера в зависимости от того, насколько ваш синтаксис соответствует существующему Python. Хороший промежуточный подход - использовать модуль токенизатора. Это должно позволить вам добавлять новые ключевые слова, управляющие структуры и т. Д. При интерпретации источника аналогично интерпретатору python, тем самым избегая поломки грубых решений регулярных выражений. Для приведенного выше «myprint» вы можете написать следующий код преобразования:
(Это действительно делает myprint ключевым словом, поэтому использование в качестве переменной в другом месте может вызвать проблемы)
Тогда проблема в том, как его использовать, чтобы ваш код можно было использовать из Python. Один из способов - написать собственную функцию импорта и использовать ее для загрузки кода, написанного на вашем пользовательском языке. то есть:
Однако это требует, чтобы вы обрабатывали свой настроенный код иначе, чем обычные модули Python. т.е. "
some_mod = myimport("some_mod.py")
" а не "import some_mod
"Еще одно довольно изящное (хотя и хакерское) решение - создать собственную кодировку (см. PEP 263 ), как демонстрирует этот рецепт. Вы можете реализовать это как:
Теперь, после запуска этого кода (например, вы можете поместить его в свой .pythonrc или site.py), любой код, начинающийся с комментария «# coding: mylang», будет автоматически переведен с помощью вышеуказанного шага предварительной обработки. например.
Предостережения:
У препроцессорного подхода есть проблемы, с которыми вы, вероятно, знакомы, если работали с препроцессором C. Главный из них - отладка. Все, что видит python, - это предварительно обработанный файл, что означает, что текст, напечатанный в трассировке стека и т. Д., Будет ссылаться на него. Если вы выполнили значительный перевод, он может сильно отличаться от исходного текста. В приведенном выше примере не меняются номера строк и т. Д., Поэтому он не будет сильно отличаться, но чем больше вы его измените, тем сложнее будет выяснить.
источник
myimport
в модуле, который просто содержит,print 1
поскольку это только строка кода, дает=1 ... SyntaxError: invalid syntax
b=myimport("b.py")
" и b.py, содержащими только "print 1
". Есть ли что-то еще в ошибке (трассировка стека и т. д.)?import
использует встроенный__import__
, поэтому, если вы перезапишете это ( перед импортом модуля, который требует модифицированного импорта), вам не понадобится отдельныйmyimport
Да, в некоторой степени это возможно. Есть модуль , который использует
sys.settrace()
для реализацииgoto
иcomefrom
"ключевых слов":источник
Короткие изменения и перекомпиляции исходного кода (который является возможным с открытым исходным кодом), изменяя базовый язык не представляется возможным.
Даже если вы перекомпилируете исходный код, это будет не python, а просто ваша взломанная измененная версия, в которую вам нужно быть очень осторожным, чтобы не внести в нее ошибки.
Однако я не уверен, зачем вам это нужно. Объектно-ориентированные функции Python позволяют довольно просто достичь аналогичных результатов с языком в его нынешнем виде.
источник
Общий ответ: вам нужно предварительно обработать исходные файлы.
Более конкретный ответ: установите EasyExtend и выполните следующие действия.
i) Создайте новый ланглет (язык расширения)
Без дополнительных указаний в EasyExtend / langlets / mystmts / создается куча файлов.
ii) Откройте mystmts / parsedef / Grammar.ext и добавьте следующие строки
Этого достаточно, чтобы определить синтаксис вашего нового оператора. Нетерминал small_stmt является частью грамматики Python, и это место, где подключается новый оператор. Теперь синтаксический анализатор распознает новый оператор, то есть исходный файл, содержащий его, будет проанализирован. Однако компилятор отклонит его, потому что его еще нужно преобразовать в действительный Python.
iii) Теперь нужно добавить семантику оператора. Для этого нужно отредактировать msytmts / langlet.py и добавить посетителя узла my_stmt.
iv) перейдите к langlets / mystmts и введите
Теперь должен быть запущен сеанс, и можно использовать вновь определенный оператор:
Довольно много шагов, чтобы прийти к тривиальному утверждению, верно? Еще нет API, который позволяет определять простые вещи, не заботясь о грамматике. Но EE очень надежен с учетом некоторых ошибок. Так что появление API, позволяющего программистам определять удобные вещи, такие как инфиксные операторы или небольшие операторы, с помощью простого объектно-ориентированного программирования - лишь вопрос времени. Для более сложных вещей, таких как встраивание целых языков в Python с помощью построения ланглета, невозможно обойтись без подхода с использованием полной грамматики.
источник
Вот очень простой, но дрянной способ добавления новых операторов только в режиме интерпретации . Я использую его для небольших однобуквенных команд для редактирования аннотаций генов, используя только sys.displayhook, но для того, чтобы я мог ответить на этот вопрос, я также добавил sys.excepthook для синтаксических ошибок. Последнее действительно уродливо, извлекает необработанный код из буфера строки чтения. Преимущество состоит в том, что таким образом легко добавлять новые операторы.
источник
Я нашел руководство по добавлению новых операторов:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
В основном, чтобы добавить новые операторы, вы должны отредактировать
Python/ast.c
(среди прочего) и перекомпилировать двоичный файл python.Хотя это возможно, не делайте этого. Вы можете достичь почти всего с помощью функций и классов (которые не требуют от людей перекомпиляции python только для запуска вашего скрипта ..)
источник
Сделать это можно с помощью EasyExtend :
источник
Это не совсем добавление новых операторов в синтаксис языка, но макросы - мощный инструмент: https://github.com/lihaoyi/macropy
источник
Не обошлось и без модификации интерпретатора. Я знаю, что многие языки за последние несколько лет были описаны как «расширяемые», но не так, как вы описываете. Вы расширяете Python, добавляя функции и классы.
источник
Существует язык, основанный на Python, под названием Logix, с которым вы МОЖЕТЕ делать такие вещи. Некоторое время он не разрабатывался, но функции, которые вы просили , работают с последней версией.
источник
Некоторые вещи можно сделать с помощью декораторов. Например, предположим, что у Python не было
with
инструкции. Затем мы могли бы реализовать подобное поведение, например:Однако это довольно нечистое решение. Особенно неожиданно поведение, когда декоратор вызывает функцию и устанавливает
_
ееNone
. Для пояснения: этот декоратор эквивалентен написаниюа декораторы обычно модифицируют, а не выполняют функции.
Раньше я использовал такой метод в скрипте, где мне приходилось временно устанавливать рабочий каталог для нескольких функций.
источник
Десять лет назад вы не могли этого сделать, и я сомневаюсь, что это изменилось. Однако тогда было не так уж сложно изменить синтаксис, если вы были готовы перекомпилировать python, и я сомневаюсь, что это тоже изменилось.
источник