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


124

Вы можете добавлять новые заявления (как print, raise, with) синтаксис языка Python?

Скажем, разрешить ..

mystatement "Something"

Или,

new_if True:
    print "example"

Не столько, если нужно , а скорее, если это возможно (если не считать изменения кода интерпретаторов Python)


10
В некотором роде примечание, один вариант использования, где было бы удобно создавать новые операторы на лету (в отличие от серьезного «расширения» языка), предназначен для людей, которые используют интерактивный интерпретатор в качестве калькулятора или даже оболочки ОС. , Я часто на лету создаю небольшие одноразовые функции, чтобы сделать что-то, что я собираюсь повторить, и в таких ситуациях было бы неплохо создавать очень сокращенные команды, такие как макросы или операторы, вместо того, чтобы вводить длинные имена с синтаксисом function (). Конечно, Py не для этого ... но люди проводят много времени, используя его в интерактивном режиме.
Kilo

5
@Kilo, возможно, стоит взглянуть на ipython - у него много функций оболочки, например, вы можете использовать обычные команды «ls» и «cd», завершение табуляции, множество функций макросов и т. Д.
dbr

Некоторые языки прекрасно расширяемы, например Forth и Smalltalk, но их языковые парадигмы также отличаются от используемых в Python. С обоими этими словами любые новые слова (Forth) или методы (Smalltalk) становятся неотъемлемой, неотличимой частью языка для этой установки. Таким образом, каждая установка Forth или Smalltalk со временем становится уникальным творением. Также Форт основан на RPN. Но если мыслить в духе DSL, нечто подобное должно быть реализовано в Python. Хотя, как здесь говорили другие, почему?

1
Как человек, свободно владеющий как Python, так и Forth, и реализовавший несколько компиляторов Forth в прошлые годы, я могу внести здесь определенный вклад. Без прямого доступа к внутреннему парсеру Python это совершенно невозможно. Вы можете подделать его с помощью предварительной обработки, как иллюстрируют (откровенно говоря, довольно гладкие!) Ответы ниже, но по-настоящему обновить синтаксис и / или семантику языка в горячем интерпретаторе невозможно. Это и проклятие Python, и его преимущество перед Lisp- и Forth-подобными языками.
Сэмюэл А. Фалво II 04

Ответы:


153

Вы можете найти это полезным - Внутреннее устройство 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предложение циклов и я не думаю, что он хорошо сочетается с 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:

| 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

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

until condition:
   # do stuff

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

while not condition:
  # do stuff

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

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

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

Вывод

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

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

Ссылки

Для построения этой статьи я использовал несколько отличных ссылок. Вот они, в произвольном порядке:

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

первоисточник


7
Отличная статья (/ блог), спасибо! Принимаю, поскольку это полностью отвечает на вопрос, а ответы «не делайте этого» / «coding: mylang» уже получили большое количество голосов, поэтому они будут выглядеть красиво в порядке \ o /
dbr

1
Но, к сожалению, это не ответ. Связанная статья есть, но вы не можете проголосовать за нее или принять ее. Ответы, состоящие исключительно из ссылки, не приветствуются.
Alfe

6
@Alfe: это было опубликовано два года назад, принято и поставлено +1 16 читателями. Обратите внимание, что он ссылается на мой собственный блог, и я не собираюсь копировать большую статью в StackOverflow. Не стесняйтесь делать это в полезном редактировании, а не в роли полицейского.
Эли Бендерский

2
@EliBendersky Useful - это преуменьшение для этой статьи. Спасибо, что так много объяснили, как эти вещи на самом деле работают в Python. Это действительно помогло мне понять AST, который имеет отношение к моей текущей работе. ** также, если вам интересно, моя версия untilis isa/ isanas in if something isa dict:orif something isan int:
Inversus

5
Су, этот ответ - «Напишите и скомпилируйте свой собственный язык из исходного кода, разветвленного из python»
ThorSummoner

53

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


12
Хороший! Вместо того, чтобы сказать «не может быть дан», вы на самом деле даете несколько хороших ответов (которые сводятся к «вы действительно не хотите этого делать»).
c0m4

Я не уверен, что понимаю, как работает первый пример - попытка использовать myimportв модуле, который просто содержит, print 1поскольку это только строка кода, дает=1 ... SyntaxError: invalid syntax
olamundo

@noam: не уверен, что у вас не получается - здесь я просто получаю «1», как и ожидалось. (Это с двумя блоками, начинающимися с "import tokenize" и "import new" выше, помещенными в файл a.py, а также " b=myimport("b.py")" и b.py, содержащими только " print 1". Есть ли что-то еще в ошибке (трассировка стека и т. д.)?
Брайан

3
Python3, похоже, не позволяет этого, хотя и не обязательно специально; Я получаю ошибку спецификации.
Tobu

обратите внимание, что importиспользует встроенный __import__, поэтому, если вы перезапишете это ( перед импортом модуля, который требует модифицированного импорта), вам не понадобится отдельныйmyimport
Тобиас Кинцлер

21

Да, в некоторой степени это возможно. Есть модуль , который использует 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
Хотя это не совсем новый синтаксис ... просто похоже.
Hans Nowak

3
-1: На указанной странице есть заголовок: «Модуль 'goto' был первоапрельской шуткой, опубликован 1 апреля 2004 года. Да, он работает, но, тем не менее, это шутка. Пожалуйста, не используйте его в реальном коде!»
Джим

6
@Jim может пересмотреть оценку -1. он намекает вам на механизм реализации. приятная вещь для начала.
n611x007

14

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

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

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


2
Я не согласен с одним пунктом. Если вы добавите новые ключевые слова, я думаю, это все равно будет Python. Если вы измените существующие ключевые слова, это, как вы говорите, просто взлом.
Bill the Lizard

9
Если вы добавите новые ключевые слова, это будет язык, производный от Python. Если вы измените ключевые слова, это будет язык, несовместимый с Python.
tzot

1
Если вы добавляете ключевые слова, вы, возможно, упускаете из виду «простой легкий в освоении синтаксис» и «обширные библиотеки». Я думаю, что особенности языка почти всегда являются ошибкой (например, COBOL, Perl и PHP).
S.Lott

5
Новые ключевые слова нарушат код Python, который использует их в качестве идентификаторов.
akaihola

12

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

Более конкретный ответ: установите 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 с помощью построения ланглета, невозможно обойтись без подхода с использованием полной грамматики.


11

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


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

4

Я нашел руководство по добавлению новых операторов:

https://troeger.eu/files/teaching/pythonvm08lab.pdf

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

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


Настоящая ссылка на PDF - эта «автоконверсия» не работает, и Бог знает, давно уже не работает: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

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

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


1
После перехода по этой ссылке открывается страница: «EasyExtend мертв. Для тех, кто интересуется EE, есть следующий проект под названием Langscape Different name, полный редизайн, тот же путь». Поскольку существует опасность, что эта информационная страница может перестать работать, возможно, стоит обновить ответ.
celtschk


1

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


1

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


Звучит интересно, но, похоже, умер примерно в 2009 году: web.archive.org/web/20090107014050/http://livelogix.net/logix
Тобиас Кинцлер

1

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

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

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


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.