你能为Python的语法添加新的语句吗?

时间:2008-10-18 10:47:22

标签: python syntax language-specifications

您可以在Python的语法中添加新语句(例如printraisewith)吗?

说,允许..

mystatement "Something"

或者,

new_if True:
    print "example"

如果你应该,那不是很多,而是如果可能的话(没有修改python解释器代码)

12 个答案:

答案 0 :(得分:51)

执行此类操作的一种方法是预处理源并对其进行修改,将添加的语句转换为python。这种方法会带来各种各样的问题,我不推荐它用于一般用法,但对于语言实验或特定目的的元编程,它偶尔会有用。

例如,假设我们要引入一个“myprint”语句,而不是打印到屏幕而是登录到特定文件。即:

myprint "This gets logged to file"

等同于

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

有关如何进行替换的各种选项,从正则表达式替换到生成AST,再到编写自己的解析器,具体取决于语法与现有python的匹配程度。一个好的中间方法是使用tokenizer模块。这应该允许您在解释源代码时添加新的关键字,控制结构等,类似于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

另一个相当简洁(尽管是hacky)的解决方案是创建一个自定义编码(请参阅PEP 263),如this配方演示。您可以将其实现为:

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看到的都是预处理文件,这意味着在堆栈跟踪等中打印的文本将引用该文件。如果您执行了重要的翻译,这可能与您的源文本有很大不同。上面的例子不会改变行号等,因此不会有太大的不同,但是你改变的越多,就越难以弄明白。

答案 1 :(得分:20)

是的,在某种程度上是可能的。有一个module使用sys.settrace()来实现gotocomefrom“关键字”:

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"

答案 2 :(得分:14)

如果没有更改和重新编译源代码(使用开源可能 ),则无法更改基本语言。

即使你重新编译源代码,它也不会是python,只是你的hacked-up更改版本,你需要非常小心,不要引入bug。

但是,我不确定你为什么要这样做。 Python的面向对象特性使得使用现有语言获得类似结果非常简单。

答案 3 :(得分:12)

一般答案:您需要预处理源文件。

更具体的答案:安装EasyExtend,然后执行以下步骤

i)创建一个新的langlet(扩展语言)

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编程来定义诸如中缀运算符或小语句等方便的东西。对于更复杂的事情,例如通过构建langlet在Python中嵌入整个语言,无法绕过完整的语法方法。

答案 4 :(得分:9)

这是一种非常简单但糟糕的方式来添加新语句,仅在解释模式下。我正在使用它来进行小的单字母命令,仅使用sys.displayhook来编辑基因注释,但我只能回答这个问题,我也为语法错误添加了sys.excepthook。后者非常难看,从readline缓冲区获取原始代码。好处是,以这种方式添加新语句非常容易。


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

答案 5 :(得分:4)

我找到了添加新语句的指南:

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

基本上,要添加新语句,您必须编辑Python/ast.c(以及其他内容)并重新编译python二进制文件。

虽然有可能,但不要。你可以通过函数和类来实现几乎所有东西(不需要人们重新编译python就可以运行你的脚本了。)

答案 6 :(得分:3)

可以使用EasyExtend

执行此操作
  

EasyExtend(EE)是一个预处理器   发生器和元编程   用纯Python和Python编写的框架   与CPython集成。主要的   EasyExtend的目的是创造   扩展语言,即添加   Python的自定义语法和语义。

答案 7 :(得分:2)

不是没有修改解释器。我知道过去几年中有很多语言被描述为“可扩展”,但不是你所描述的方式。您可以通过添加函数和类来扩展Python。

答案 8 :(得分:2)

有一种基于python的语言Logix,您可以使用它来执行此类操作。它暂时没有开发,但是您要求使用最新版本的功能

答案 9 :(得分:2)

装饰器可以完成一些事情。我们举个例子假设,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.

和装饰器通常需要修改而不是执行函数。

我之前在脚本中使用过这样的方法,我必须暂时为多个函数设置工作目录。

答案 10 :(得分:2)

这并不是在语言语法中添加新语句,但宏是一个强大的工具:https://github.com/lihaoyi/macropy

答案 11 :(得分:0)

十年前你不能,我怀疑这种情况有所改变。但是,如果您准备重新编译python,那么修改语法并不难,我怀疑它是否已经改变了。