Чи можете ви додати нові твердження до синтаксису Python?


124

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

Скажіть, щоб дозволити ..

mystatement "Something"

Або,

new_if True:
    print "example"

Не стільки, якщо потрібно , а скоріше, якщо це можливо (окрім зміни коду інтерпретаторів python)


10
У дещо пов'язаній примітці, один випадок використання, коли було б зручно створювати нові заяви на льоту (на відміну від серйозного "розширення" мови) для людей, які використовують інтерактивний інтерпретатор як калькулятор, або навіть оболонку ОС . Я часто створюю невеликі функції викиду на льоту, щоб зробити щось, що я збираюся повторити, і в таких ситуаціях було б непогано створювати дуже скорочені команди, такі як макроси або заяви, а не вводити довгі імена з синтаксисом function (). Звичайно, це не насправді для Py, але люди витрачають багато часу на його інтерактивне використання.
Кіло

5
@Kilo, можливо, варто звернути увагу на ipython - він має багато shell'ish-функцій, наприклад, ви можете використовувати звичайні команди "ls" і "cd", доповнення вкладки, багато функцій макро-ish і т.д. тощо
dbr

Деякі мови є вишукано розширюваними, наприклад, Forth та Smalltalk, але їхні мовні парадигми відрізняються від тих, які використовуються також Python. З обома цими будь-які нові слова (Forth) або методи (Smalltalk) стають невід'ємною, нерозбірливою частиною мови для цієї установки. Тож кожна установка Forth або Smalltalk з часом стає унікальним творінням. Також Forth базується на RPN. Але мислячи за принципами DSL, щось подібне повинно бути виконаним у Python. Хоча, як тут казали інші, чому?

1
Оскільки хтось вільно володіє і Python, і Forth, і хто впровадив декілька компіляторів Forth за останні роки, я можу внести свій внесок у цю програму з певною мірою авторитету. Без отримання доступу до внутрішнього аналізатора Python це абсолютно неможливо. Ви можете підробити це шляхом попередньої обробки, як показано (відверто кажучи, досить гладкий!) Відповіді нижче, але по-справжньому оновити синтаксис та / або семантику мови в гарячому інтерпретаторі неможливо. Це як прокляття Python, так і його перевага перед мовами, схожими на Lisp і Forth.
Самуель А. Фальво II

Відповіді:


153

Це може бути вам корисним - внутрішні програми Python: додавання нового твердження до Python , цитується тут:


Ця стаття - це спроба краще зрозуміти, як працює передній край Python. Просто читання документації та вихідного коду може бути трохи нудним, тому я тут дотримуюся практичного підходу: я збираюся додати untilзаяву до Python.

Все кодування цієї статті було зроблено проти передової гілки Py3k у дзеркалі сховища Python Mercurial .

untilзаяву

У деяких мовах, як у Ruby, є untilтвердження, яке є доповненням while( until num == 0еквівалентно while num != 0). У Рубі я можу написати:

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 надаються відносно кореня дерева джерела, що є каталогом, де ви запускаєте конфігурацію та створюєте для складання 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яка визначає структуру ASTs 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-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 VM заснований на стеці, і заглянувши в документацію 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значення перерахунку не обробляється в операторі перемикача symtable_visit_stmtі скаржиться. Завжди важливо перевірити наявність попереджень компілятора!

І зараз ми справді робимо. Складання джерела після цієї зміни робить виконання myfoo(3)роботи таким, як очікувалося.

Висновок

У цій статті я продемонстрував, як додати нову заяву до Python. Хоча це вимагає зовсім трохи повороту в коді компілятора Python, зміни не було важко здійснити, тому що я використовував аналогічний та існуючий вислів як настанову.

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

Список літератури

Для побудови цієї статті я використав кілька чудових посилань. Ось вони, не в певному порядку:

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

першоджерело


7
Відмінна стаття (/ блог), дякую! Беручи , так як це прекрасно відповідає на питання, і «не робити» / «кодування: mylang» відповіді вже досить upvoted, так буде здаватися добре, щоб \ о /
DBR

1
Але, на жаль, це не відповідь. Пов’язана стаття є, але її ви не можете підтвердити або прийняти. Відповіді, що складаються повністю лише з посилання, не враховують.
Альфе

6
@Alfe: це було опубліковано два роки тому, його прийняли і поставили +1 читачам. Зауважте, що це посилання на мою власну публікацію в блозі, і копіювання великої статті в StackOverflow - це не те, що я маю намір робити. Не соромтеся робити це в корисній редакції, а не граючи в поліцію.
Елі Бендерський

2
@EliBendersky Корисне досить занижене для цієї статті. Дякуємо, що так багато пояснили, як ці речі насправді працюють у python. Це справді допомогло мені зрозуміти AST, що стосується моєї поточної роботи. ** Також, якщо вам цікаво, моя версія untilє isa/ isanяк в if something isa dict:абоif 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

Це вимагає, щоб ви обробляли свій індивідуальний код інакше, ніж звичайні модулі пітона. тобто "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
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", як очікувалося. (Це з двома блоками, що починаються "імпортувати токенізацію" та "імпортувати нові", що надсилаються у файл a.py, а також " b=myimport("b.py")" та b.py, що містять просто " print 1". Чи є ще щось до помилки (трасування стека тощо)?
Брайан

3
Здається, Python3 цього не дозволяє, хоча й не обов'язково спеціально; Я отримую помилку BOM.
Тобу

зауважте, що 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
Це насправді не новий синтаксис ... це просто схоже.
Ганс Новак

3
-1: На пов'язаній сторінці є заголовок: "Модуль" goto "- це жарт" Першого квітня ", опублікований 1 квітня 2004 р. Так, це працює, але все-таки це жарт. Будь ласка, не використовуйте його в реальному коді!"
Джим

6
@Jim може переглянути -1. це натякає на механізм реалізації. приємно почати.
n611x007

14

Не вистачає змін та перекомпіляції вихідного коду (що є можливим з відкритим вихідним кодом), змінюючи базовий язик не представляється можливим.

Навіть якщо ви перекомпілюєте джерело, це не був би python, а лише ваша змінена версія, яку ви зламали, і вам потрібно бути дуже обережним, щоб не вводити помилки.

Однак я не впевнений, чому ви цього хочете. Об'єктно-орієнтовані функції Python дозволяють досягти подібних результатів з мовою, якою вона є, досить просто.


2
Я не погоджуюся в одному питанні. Якщо ви додасте нові ключові слова, я думаю, що це все ще був би Python. Якщо ви змінюєте існуючі ключові слова, то, як ви кажете, це просто злом.
Білл Ящірка

9
Якщо ви додасте нові ключові слова, це буде мова походження Python. Якщо ви зміните ключові слова, це буде не сумісна з Python мова.
tzot

1
Якщо ви додасте ключові слова, можливо, вам не вистачає пункту "простий у навчанні синтаксис" та "широкі бібліотеки". Я думаю, що особливості мови майже завжди є помилкою (приклади включають COBOL, Perl та PHP).
С.Лотт

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) cd до 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, який дозволяє програмістам визначати зручні речі, такі як оператори інфіксації або невеликі оператори, використовуючи просто зручне програмування OO. Для складніших речей, таких як вбудовування цілих мов у Python за допомогою побудови шматка, немає можливості обійти повний граматичний підхід.


11

Ось дуже простий, але хитрий спосіб додавати нові заяви, лише в інтерпретаційному режимі . Я використовую його для невеликих команд з 1 літери для редагування анотацій генів, використовуючи лише 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 просто для запуску вашого сценарію ..)


Справжнє посилання на 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.