【问题标题】:Can you add new statements to Python's syntax?你能在 Python 的语法中添加新的语句吗?
【发布时间】:2010-09-17 21:51:41
【问题描述】:

你能在 Python 的语法中添加新的语句(如printraisewith)吗?

说,允许..

mystatement "Something"

或者,

new_if True:
    print "example"

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

【问题讨论】:

  • 在一些相关的说明中,一个可以方便地动态创建新语句(而不是认真“扩展”语言)的用例是使用交互式解释器的人计算器,甚至是操作系统外壳。我经常在运行中创建小的一次性函数来做一些我将要重复的事情,在这些情况下,创建非常简短的命令(如宏或语句)而不是使用 function() 语法输入长名称会很好。当然,这并不是 Py 的真正用途……但人们确实花了很多时间以交互方式使用它。
  • @Kilo 可能值得看看 ipython - 它有很多 shell'ish 功能,例如你可以使用常规的“ls”和“cd”命令、制表符补全、很多宏-ish 功能等
  • 有些语言的扩展性非常好,例如Forth 和 Smalltalk,但它们的语言范式也不同于 Python 使用的范式。有了这两者,任何新词 (Forth) 或方法 (Smalltalk) 都成为该安装语言中不可或缺的、难以区分的部分。因此,随着时间的推移,每个 Forth 或 Smalltalk 安装都会成为独特的创作。 Forth 也是基于 RPN 的。但是按照 DSL 的思路思考,这样的事情应该可以在 Python 中完成。不过,正如其他人在这里所说,为什么?
  • 作为一个精通 Python 和 Forth 的人,并且在过去几年中实现了多个 Forth 编译器,我可以在这里以某种程度的权威做出贡献。如果没有获得对 Python 内部解析器的原始访问权限,这是完全不可能的。您可以通过预处理来伪造它,如下面的(坦率地说,相当巧妙!)答案所示,但在热解释器中真正更新语言的语法和/或语义是不可能的。这既是 Python 的祸根,也是它相对于 Lisp 和 Forth 类语言的优势。

标签: python syntax language-specifications


【解决方案1】:

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

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

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

虽然有可能,但不要这样做。您可以通过函数和类实现几乎所有内容(不需要人们重新编译 python 来运行您的脚本..)

【讨论】:

【解决方案2】:

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

【讨论】:

    【解决方案3】:

    有些事情可以用装饰器来完成。让我们例如假设,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.
    

    通常期望装饰器修改而不是执行函数。

    我之前在脚本中使用过这样的方法,我不得不为几个函数临时设置工作目录。

    【讨论】:

      【解决方案4】:

      这是一种非常简单但很糟糕的添加新语句的方法,仅在解释模式下。我将它用于仅使用 sys.displayhook 编辑基因注释的小 1 字母命令,但为了回答这个问题,我也为语法错误添加了 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】:

        您可能会发现这很有用 - Python internals: adding a new statement to Python,在此引用:


        本文试图更好地理解 Python 前端的工作原理。仅仅阅读文档和源代码可能会有点无聊,所以我在这里采取了动手的方法:我将在 Python 中添加一个until 语句。

        本文的所有编码都是针对 Python Mercurial repository mirror 中的尖端 Py3k 分支完成的。

        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
        

        语言倡导题外话

        本文无意建议在 Python 中添加 until 语句。虽然我认为这样的声明会使一些代码更清晰,并且本文展示了添加是多么容易,但我完全尊重 Python 的极简主义哲学。实际上,我在这里想要做的只是深入了解 Python 的内部工作原理。

        修改语法

        Python 使用名为 pgen 的自定义解析器生成器。这是一个 LL(1) 解析器,可将 Python 源代码转换为解析树。解析器生成器的输入是文件Grammar/Grammar[1]。这是一个指定 Python 语法的简单文本文件。

        [1]:从这里开始,对 Python 源代码中文件的引用相对于源代码树的根目录给出,该目录是您运行 configure 和 make 以构建 Python 的目录。

        必须对语法文件进行两次修改。首先是为until 语句添加定义。我找到了 while 语句的定义位置 (while_stmt),并在 [2] 下方添加了 until_stmt

        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 完成,因此它是一个非常好的指导方针。

        请注意,我决定从我对until 的定义中排除else 子句,只是为了让它有点不同(坦率地说,我不喜欢循环中的else 子句并且不认为它非常适合 Python 之禅)。

        第二个更改是修改compound_stmt 的规则以包含until_stmt,如您在上面的sn-p 中所见。又是while_stmt 之后。

        修改Grammar/Grammar后运行make时,注意运行pgen程序重新生成Include/graminit.hPython/graminit.c,然后重新编译了几个文件。

        修改AST生成代码

        在 Python 解析器创建解析树后,此树将转换为 AST,因为在编译过程的后续阶段 AST 为 much simpler to work with

        因此,我们将访问定义 Python AST 结构的 Parser/Python.asdl,并为我们的新 until 语句添加一个 AST 节点,同样位于 while 的正下方:

        | While(expr test, stmt* body, stmt* orelse)
        | Until(expr test, stmt* body)
        

        如果您现在运行make,请注意在编译一堆文件之前,运行Parser/asdl_c.py 以从AST 定义文件生成C 代码。这(如Grammar/Grammar)是另一个使用迷你语言(换句话说,DSL)来简化编程的 Python 源代码示例。另请注意,由于Parser/asdl_c.py 是 Python 脚本,因此这是一种 bootstrapping - 要从头开始构建 Python,Python 必须已经可用。

        虽然Parser/asdl_c.py 生成了管理我们新定义的 AST 节点的代码(在文件 Include/Python-ast.hPython/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的新节点。

        请注意,我们使用NCHCHILD 等宏来访问解析树节点n。这些值得理解——他们的代码在Include/node.h

        题外话:AST 作文

        我选择为until 语句创建一种新类型的AST,但实际上这不是必需的。我本可以使用现有 AST 节点的组合来节省一些工作并实现新功能,因为:

        until condition:
           # do stuff
        

        在功能上等同于:

        while not condition:
          # do stuff
        

        我可以创建一个带有While 节点的Not 节点,而不是在ast_for_until_stmt 中创建Until 节点。由于 AST 编译器已经知道如何处理这些节点,因此可以跳过该过程的后续步骤。

        将 AST 编译成字节码

        下一步是将 AST 编译成 Python 字节码。编译有一个中间结果,它是一个 CFG(控制流图),但由于相同的代码处理它,我现在将忽略这个细节并将其留给另一篇文章。

        我们接下来要看的代码是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。事实上,简短的调查将我们引向Python/compile.c 中定义的VISIT 宏:

        #define VISIT(C, TYPE, V) {\
            if (!compiler_visit_ ## TYPE((C), (V))) \
                return 0; \
        

        用于在compiler_body 中调用compiler_visit_stmt。然而,回到我们的业务......

        正如承诺的那样,这里是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 字节码的深刻理解而编写的。与本文的其余部分一样,它是模仿 kin compiler_while 函数完成的。但是,通过仔细阅读它,记住 Python VM 是基于堆栈的,并查看dis 模块的文档,其中有a list of Python bytecodes 的描述,就可以理解发生了什么。

        就是这样,我们已经完成了......不是吗?

        完成所有更改并运行 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 时执行的步骤之一是为其编译的代码创建符号表。在PyAST_Compile 中对PySymtable_Build 的调用调用符号表模块(Python/symtable.c),它以类似于代码生成函数的方式遍历AST。每个作用域都有一个符号表有助于编译器找出一些关键信息,例如哪些变量是全局的,哪些是作用域的局部变量。

        为了解决这个问题,我们必须修改Python/symtable.c中的symtable_visit_stmt函数,在while语句的类似代码之后添加处理until语句的代码[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 的switch 语句中处理并抱怨。检查编译器警告总是很重要的!

        现在我们真的完成了。在此更改之后编译源代码会使myfoo(3) 的执行按预期工作。

        结论

        在本文中,我演示了如何向 Python 添加新语句。尽管需要对 Python 编译器的代码进行大量修改,但更改并不难实现,因为我使用了类似的现有语句作为指导。

        Python 编译器是一个复杂的软件块,我并不声称自己是这方面的专家。但是,我对 Python 的内部结构非常感兴趣,尤其是它的前端。因此,我发现这个练习对于编译器原理和源代码的理论研究非常有用。它将作为将来深入了解编译器的文章的基础。

        参考文献

        我使用了一些优秀的参考来构建这篇文章。它们在这里,没有特别的顺序:

        • PEP 339: Design of the CPython compiler - 可能是 Python 编译器的官方文档中最重要和最全面的部分。它非常简短,令人痛苦地表明 Python 内部缺乏良好的文档。
        • “Python 编译器内部” - Thomas Lee 的文章
        • “Python:设计和实现” - Guido van Rossum 的演讲
        • Python (2.5) 虚拟机,导览 - Peter Tröger 的演示

        original source

        【讨论】:

        • 优秀的文章(/blog),谢谢​​!接受,因为这完美地回答了问题,并且“不要那样做”/“编码:mylang”的答案已经被高度赞成,所以会很好地按顺序显示 \o/
        • 但不幸的是,这不是答案。链接的文章是,但您不能赞成或接受。不鼓励完全由链接组成的答案。
        • @Alfe:这是两年前发布的,已被 16 位读者接受并 +1。请注意,它链接到我自己的博客文章,并且我不打算将一篇大文章复制到 StackOverflow 中。随意在有用的编辑中这样做,而不是扮演警察。
        • @EliBendersky 有用对于那篇文章来说是相当轻描淡写的。感谢您解释了这些东西在 python 中的实际工作方式。这确实帮助我理解了与我目前的工作相关的 AST。 **另外,如果您好奇,我的until 版本是isa/isan,如if something isa dict:if something isan int:
        • Soo,这个答案是“从源代码编写和编译你自己的语言,从 python 分叉”
        【解决方案6】:

        只需更改和重新编译源代码(这开源可能的),更改基础语言是不可能的。

        即使你确实重新编译了源代码,它也不会是 python,只是你修改后的版本,你需要非常小心,不要将错误引入其中。

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

        【讨论】:

        • 我有一点不同意。如果您 add 新关键字,我认为它仍然是 Python。如果您更改现有关键字,那么正如您所说的那样,这只是被破坏了。
        • 如果添加新关键字,它将是 Python 派生的语言。如果您更改关键字,它将是与 Python 不兼容的语言。
        • 如果您添加关键字,您可能会错过“简单易学的语法”和“丰富的库”这一点。我认为语言特性几乎总是一个错误(例子包括 COBOL、Perl 和 PHP)。
        • 新关键字会破坏使用它们作为标识符的 Python 代码。
        【解决方案7】:

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

        更具体的答案:安装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 中,没有办法绕过完整的语法方法。

        【讨论】:

          【解决方案8】:

          可以使用EasyExtend

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

          【讨论】:

          • 点击那个链接现在会出现一个页面:“EasyExtend 已经死了。对于那些对 EE 感兴趣的人,有一个名为 Langscape 的后续项目不同的名称,完全重新设计,相同的旅程。”由于此信息页面可能会死机,因此更新答案可能是个好主意。
          【解决方案9】:

          是的,在某种程度上是可能的。有一个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"
          

          【讨论】:

          • 虽然这不是真正的新语法......它只是看起来像。
          • -1:链接页面的标题是:“'goto' 模块是愚人节的玩笑,于 2004 年 4 月 1 日发布。是的,它有效,但它仍然是个玩笑。请不要'不要在实际代码中使用它!”
          • @Jim 可能会重新考虑 -1。它提示您执行机制。不错的开始。
          【解决方案10】:

          有一种基于 python 的语言 Logix 可以用来做这些事情。它已经有一段时间没有开发了,但是您要求的功能确实可以使用最新版本。

          【讨论】:

          【解决方案11】:

          执行此类操作的一种方法是预处理源代码并对其进行修改,将添加的语句转换为 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

          另一个相当简洁(尽管很老套)的解决方案是创建自定义编码(参见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 看到的是预处理文件,这意味着堆栈跟踪等中打印的文本将引用该文件。如果您进行了重要的翻译,这可能与您的源文本有很大不同。上面的例子并没有改变行号等,所以不会有太大的不同,但是你改变的越多,就越难弄清楚。

          【讨论】:

          • 不错的一个!你实际上给出了一些好的答案,而不是说“不能被拒绝”(归结为“你真的不想这样做”)投票。
          • 我不确定我是否理解第一个示例的工作原理 - 尝试在仅包含 print 1 的模块上使用 myimport,因为它只有一行代码会产生 =1 ... SyntaxError: invalid syntax
          • @noam:不知道你有什么问题——在这里我只是按预期打印了“1”。 (这是在文件a.py中以“import tokenize”和“import new”开头的2个块,以及“b=myimport("b.py")”和仅包含“print 1”的b.py。还有什么到错误(堆栈跟踪等)?
          • Python3 似乎不允许这样做,尽管不一定是故意的;我收到 BOM 错误。
          • 请注意 import 使用内置的 __import__,因此如果您覆盖它(导入需要修改导入的模块之前),则不需要单独的myimport
          【解决方案12】:

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

          【讨论】:

            【解决方案13】:

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

            【讨论】:

              猜你喜欢
              • 2010-09-13
              • 1970-01-01
              • 2011-04-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-12-28
              相关资源
              最近更新 更多