Можете ли вы добавить новые операторы (например, print, raise, with) в синтаксис Python?
Скажем, разрешить ..
mystatement "Something"
Или же,
new_if True:
print "example"
Не так много, если вы должен, а скорее, если это возможно (если не считать изменения кода интерпретатора Python)
@Kilo, возможно, стоит взглянуть на ipython - он имеет множество функций оболочки, например, вы можете использовать обычные команды «ls» и «cd», завершение табуляции, множество функций макросов и т. д.
Некоторые языки прекрасно расширяемы, например Forth и Smalltalk, но их языковые парадигмы также отличаются от используемых в Python. С обоими этими словами любые новые слова (Forth) или методы (Smalltalk) становятся неотъемлемой неотъемлемой частью языка для этой установки. Таким образом, каждая установка Forth или Smalltalk со временем становится уникальным творением. Также Форт основан на RPN. Но если мыслить в духе DSL, что-то подобное должно быть реализовано в Python. Хотя, как здесь говорили другие, почему?
Как человек, свободно владеющий как Python, так и Forth, и реализовавший несколько компиляторов Forth в прошлые годы, я могу внести здесь определенный вклад. Без прямого доступа к внутреннему парсеру Python это совершенно невозможно. Вы можете подделать его с помощью предварительной обработки, как показывают (откровенно говоря, довольно гладкие!) Ответы ниже, но по-настоящему обновить синтаксис и / или семантику языка в горячем интерпретаторе невозможно. Это одновременно и проклятие Python, и его преимущество перед Lisp- и Forth-подобными языками.






Десять лет назад вы не могли этого сделать, и я сомневаюсь, что это изменилось. Однако тогда было не так сложно изменить синтаксис, если вы были готовы перекомпилировать python, и я сомневаюсь, что это тоже изменилось.
За исключением изменения и перекомпиляции исходного кода (что является возможно с открытым исходным кодом), изменение базового языка на самом деле невозможно.
Даже если вы перекомпилируете исходный код, это будет не python, а просто ваша взломанная измененная версия, в которой нужно быть очень осторожным, чтобы не внести в нее ошибки.
Однако я не уверен, зачем вам это нужно. Объектно-ориентированные функции Python позволяют довольно просто достичь аналогичных результатов с языком в его нынешнем виде.
Я не согласен с одним пунктом. Если вы добавите новые ключевые слова добавлять, я думаю, это все равно будет Python. Если вы используете изменять существующих ключевых слов, то это, как вы говорите, просто взлом.
Если вы добавите новые ключевые слова, это будет язык, производный от Python. Если вы измените ключевые слова, это будет язык, несовместимый с Python.
Если вы добавляете ключевые слова, вы, возможно, упускаете из виду «простой легкий в освоении синтаксис» и «обширные библиотеки». Я думаю, что языковые функции почти всегда являются ошибкой (например, COBOL, Perl и PHP).
Новые ключевые слова нарушат код Python, который использует их в качестве идентификаторов.
Не обошлось и без модификации интерпретатора. Я знаю, что многие языки за последние несколько лет были описаны как «расширяемые», но не так, как вы описываете. Вы расширяете Python, добавляя функции и классы.
Я нашел руководство по добавлению новых заявлений:
https://troeger.eu/files/teaching/pythonvm08lab.pdf
По сути, чтобы добавить новые операторы, вы должны отредактировать Python/ast.c (среди прочего) и перекомпилировать двоичный файл python.
Хотя это возможно, не делайте этого. Вы можете добиться почти всего с помощью функций и классов (которые не требуют от людей перекомпиляции python только для запуска вашего скрипта ..)
Настоящая ссылка на PDF - эта "автоконверсия" не работает и уже давно не работает: troeger.eu/files/teaching/pythonvm08lab.pdf
Один из способов сделать это - предварительно обработать исходный код и изменить его, переведя ваш добавленный оператор в 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, - это предварительно обработанный файл, что означает, что текст, напечатанный в трассировке стека и т. д., Будет ссылаться на него. Если вы выполнили значительный перевод, он может сильно отличаться от исходного текста. В приведенном выше примере не меняются номера строк и т. д., Поэтому он не будет сильно отличаться, но чем больше вы его измените, тем сложнее будет выяснить.
Хороший! Вместо того, чтобы сказать «не может быть дан», вы на самом деле даете несколько хороших ответов (которые сводятся к «вы действительно не хотите этого делать»). Проголосуйте за.
Я не уверен, что понимаю, как работает первый пример - попытка использовать myimport в модуле, который просто содержит print 1, поскольку это только строка кода дает =1 ... SyntaxError: invalid syntax
@noam: не уверен, что у вас не получается - здесь я просто получаю «1», как и ожидалось. (Это с двумя блоками, начинающимися с «import tokenize» и «import new» выше, помещенными в файл a.py, а также с «b=myimport("b.py")» и b.py, содержащим только «print 1». Есть ли что-то еще к ошибке ( трассировка стека и т. д.)?
Python3, похоже, не допускает этого, хотя и не обязательно специально; Я получаю ошибку спецификации.
обратите внимание, что import использует встроенный __import__, поэтому, если вы его перезаписываете (до импортирует модуль, требующий модифицированного импорта), вам не нужен отдельный myimport
Разве codecs.StreamReader.__init__(self, *args, **kwargs) не должен быть super(StreamReader, self).__init__(self, *args, **kwargs)?
Существует язык, основанный на Python, под названием Logix, с которым вы МОЖЕТЕ делать такие вещи. Некоторое время он не находился в разработке, но функции, которые вы запрашивали для Выполнять работу в последней версии.
Звучит интересно, но, похоже, умер примерно в 2009 году: web.archive.org/web/20090107014050/http://livelogix.net/logi x
Да, в некоторой степени это возможно. Существует модуль, который использует 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"
Хотя это не совсем новый синтаксис ... просто похоже.
-1: На указанной странице есть заголовок: «Модуль 'goto' был первоапрельской шуткой, опубликован 1 апреля 2004 года. Да, он работает, но, тем не менее, это шутка. Пожалуйста, не используйте его в реальном коде!»
@Jim может пересмотреть оценку -1. он намекает вам на механизм реализации. приятная вещь для начала.
Это можно сделать с помощью 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, полный редизайн, тот же путь». Поскольку существует опасность, что эта информационная страница может перестать работать, возможно, стоит обновить ответ.
Общий ответ: вам нужно предварительно обработать исходные файлы.
Более конкретный ответ: установите 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
В некотором роде примечание: один вариант использования, где было бы удобно создавать новые операторы на лету (в отличие от серьезного «расширения» языка), предназначен для людей, которые используют интерактивный интерпретатор в качестве калькулятора или даже оболочки ОС. . Я часто на лету создаю небольшие одноразовые функции, чтобы сделать что-то, что я собираюсь повторить, и в таких ситуациях было бы неплохо создавать очень сокращенные команды, такие как макросы или операторы, вместо того, чтобы вводить длинные имена с синтаксисом function (). Конечно, Py не для этого ... но люди действительно проводят много времени, используя его в интерактивном режиме.