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

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

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

mystatement "Something"

Или же,

new_if True:
    print "example"

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

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

Kilo 02.11.2009 05:56

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

dbr 02.11.2009 21:57

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

user693708 06.04.2011 00:22

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

Samuel A. Falvo II 05.01.2013 01:53
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
125
4
28 625
12

Ответы 12

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

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

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

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

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

Bill the Lizard 18.10.2008 15:20

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

tzot 18.10.2008 15:48

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

S.Lott 18.10.2008 16:42

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

akaihola 22.01.2009 11:03

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

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

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

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

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

Настоящая ссылка на PDF - эта "автоконверсия" не работает и уже давно не работает: troeger.eu/files/teaching/pythonvm08lab.pdf

ZXX 16.06.2018 17:33

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

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

c0m4 19.10.2008 12:46

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

olamundo 26.06.2010 16:09

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

Brian 28.06.2010 19:47

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

Tobu 28.11.2011 15:18

обратите внимание, что import использует встроенный __import__, поэтому, если вы его перезаписываете (до импортирует модуль, требующий модифицированного импорта), вам не нужен отдельный myimport

Tobias Kienzler 04.05.2016 12:33

Разве codecs.StreamReader.__init__(self, *args, **kwargs) не должен быть super(StreamReader, self).__init__(self, *args, **kwargs)?

Tobias Kienzler 04.05.2016 12:41

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

Звучит интересно, но, похоже, умер примерно в 2009 году: web.archive.org/web/20090107014050/http://livelogix.net/logi‌ x

Tobias Kienzler 04.05.2016 12:20

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

Хотя это не совсем новый синтаксис ... просто похоже.

Hans Nowak 03.06.2010 02:59

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

Jim 27.03.2013 20:49

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

n611x007 15.07.2014 20:02

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

EasyExtend (EE) is a preprocessor generator and metaprogramming framework written in pure Python and integrated with CPython. The main purpose of EasyExtend is the creation of extension languages i.e. adding custom syntax and semantics to Python.

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

celtschk 28.10.2016 11:31

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

Более конкретный ответ: установите 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.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

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

Другие вопросы по теме