您可以在Python的语法中添加新的语句吗?


124

你可以添加新的语句(例如printraisewith)Python的语法?

说,允许

mystatement "Something"

要么,

new_if True:
    print "example"

如果您应该的话,不要太多,但如果可能的话,就可以了(只需修改python解释器代码即可)


10
在某种程度上相关的注释上,一个用例可以方便地动态创建新语句(而不是认真地“扩展”语言)是针对使用交互式解释器作为计算器甚至是OS Shell的人的。我经常动态地创建一些一次性函数来做我将要重复的事情,在那种情况下,创建非常简短的命令(例如宏或语句)会比使用function()语法键入长名称会更好。当然,这并不是Py真正的目的。.但是人们确实花费了大量时间来交互使用它。
基洛

5
@Kilo值得一看的是ipython-它具有很多shell式的功能,例如,您可以使用常规的“ ls”和“ cd”命令,制表符补全,很多宏式功能等
dbr

有些语言可以很好地扩展,例如Forth和Smalltalk,但是它们的语言范例也与Python使用的语言范例不同。有了这些,这两个新单词(Forth)或方法(Smalltalk)便成为该安装语言不可分割的一部分。因此,随着时间的推移,每个Forth或Smalltalk安装都将成为唯一的创建。Forth也是基于RPN的。但是按照DSL的思路来考虑,类似的事情应该可以在Python中完成。但是,正如其他人在这里说的那样,为什么?

1
作为精通Python和Forth的人,并且在过去几年中实现了多个Forth编译器,我可以在某种程度上为这里做出贡献。如果不获取对Python内部解析器的原始访问权限,那是完全不可能的。您可以通过预处理来伪造它,如下面的答案(坦率地说,很狡猾!),但在热解释器中无法真正更新语言的语法和/或语义。这既是Python的诅咒,也是它在类似Lisp和Forth的语言中的优势。
Samuel A. Falvo II

Answers:


153

您可能会发现这很有用-Python内部:在Python上添加新语句,引用如下:


本文旨在更好地了解Python前端的工作方式。仅阅读文档和源代码可能会有些无聊,因此我在这里采用动手实践的方法:我将向untilPython 添加一条语句。

本文的所有编码都是针对Python Mercurial存储库镜像中最前沿的Py3k分支完成的。

until声明

有些语言,如红宝石,有一个until说法,这是补充whileuntil 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

语言倡导题外话

本文并不试图建议在untilPython中添加一条语句。尽管我认为这样的声明可以使一些代码更清晰,并且本文显示了添加的难易程度,但我完全尊重Python的极简主义哲学。实际上,我在这里要做的只是深入了解Python的内部工作原理。

修改语法

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

[1]:从这里开始,相对于源代码树的根目录,对Python源文件中的文件进行引用,该目录是您运行configure和make生成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的Zen不太匹配)。

第二个更改是将规则修改为compound_stmtinclude until_stmt,如您在上面的代码段中所见。紧接着while_stmt又是。

当你运行make修改后Grammar/Grammar,通知该pgen程序运行重新生成Include/graminit.hPython/graminit.c,然后几个文件得到重新编译。

修改AST生成代码

在Python解析器创建了一个解析树之后,该树将转换为AST,因为在编译过程的后续阶段中,使用 AST 更简单

因此,我们将要访问Parser/Python.asdl,它定义了Python AST的结构,并为我们的新until语句添加了一个AST节点,再次位于以下位置while

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

如果您现在运行make,请注意,在编译一堆文件之前,请先Parser/asdl_c.py运行以从AST定义文件生成C代码。这(如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_for_expr条件表达式和语句ast_for_suite主体)以递归方式创建AST until。最后,Until返回一个名为的新节点。

请注意,我们n使用诸如NCH和的一些宏来访问解析树节点CHILD。这些值得理解-它们的代码在Include/node.h

题外话:AST组成

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

until condition:
   # do stuff

在功能上等同于:

while not condition:
  # do stuff

与其在中创建Until节点ast_for_until_stmt,不如创建一个节点作为子Not节点的While节点。由于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。确实,经过简短的调查,我们找到了以下VISIT宏中定义的宏Python/compile.c

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

它用来调用compiler_visit_stmtcompiler_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:如果条件为true,则在循环之后跳转到。这是的正确语义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_Buildin 的调用将PyAST_Compile调用符号表模块(Python/symtable.c),该模块以类似于代码生成功能的方式遍历AST。每个作用域都有一个符号表,有助于编译器找出一些关键信息,例如哪些变量是全局变量,哪些是局部变量。

为了解决这个问题,我们必须修改的symtable_visit_stmt函数,在类似语句[3]的代码之后Python/symtable.c添加用于处理until语句的代码:while

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枚举值未在和的switch语句中处理symtable_visit_stmt。检查编译器警告始终很重要!

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

结论

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

Python编译器是复杂的软件,我并不声称自己是该领域的专家。但是,我对Python的内部结构特别是前端非常感兴趣。因此,我发现此练习对于编译器原理和源代码的理论研究非常有用。它将作为以后将深入编译器的文章的基础。

参考资料

我使用了一些出色的参考来构建本文。在这里,它们没有特定的顺序:

  • PEP 339:CPython编译器的设计 -可能是Python编译器最重要,最全面的官方文档。太短了,它痛苦地显示出缺乏有关Python内部结构的好的文档。
  • “ Python编译器内部知识”-Thomas Lee的文章
  • “ Python:设计与实现”-Guido van Rossum的演讲
  • Python(2.5)虚拟机,导览-PeterTröger的演示

原始资料


7
优秀文章(/博客),谢谢!接受,因为这可以完美地回答问题,并且“不要这样做” /“编码:mylang”的回答已经被强烈推荐,因此会很好地显示在\ o /中
dbr

1
不幸的是,这不是答案。链接的文章是,但是您不能投票或接受。不鼓励完全由链接组成的答案。
Alfe 2013年

6
@Alfe:这是两年前发布的,已被16位读者接受并为其+1。请注意,它链接到我自己的博客文章,并且我不打算将大文章复制到StackOverflow中。随意进行有用的编辑,而不是扮演警察。
伊莱·班德斯基2013年

2
@EliBendersky对于该文章而言,有用的说法很轻描淡写。非常感谢您解释这些事情在python中的实际工作方式。这确实帮助我了解了AST,这与我当前的工作有关。**此外,如果您感到好奇,我的版本untilisa/ isanin if something isa dict:if something isan int:
Inversus

5
如此,答案是“从源代码编写和编译自己的语言,从python分叉”
ThorSummoner 2015年

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

这就要求您处理自定义代码的方法不同于普通的python模块。即“ some_mod = myimport("some_mod.py")”而非“ import some_mod

另一个相当整洁(尽管很hacky)的解决方案是创建自定义编码(请参阅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看到的只是经过预处理的文件,这意味着打印在堆栈跟踪等中的文本将引用该文件。如果您执行了重要的翻译,这可能与源文本有很大不同。上面的示例不会更改行号等,因此不会有太大的不同,但是更改的次数越多,越难弄清。


12
好一个!您实际上没有给出“不能傻”的答案,而是给出了一些很好的答案(归结为“您真的不想这样做”)。
c0m4

我不确定我是否理解第一个示例的工作原理-尝试myimport在仅包含print 1代码产量的模块上使用=1 ... SyntaxError: invalid syntax
olamundo 2010年

@noam:不确定您的问题是什么-在这里,我只是按预期打印了“ 1”。(这是在文件a.py,“ b=myimport("b.py")”和b.py仅包含“ ”的情况下,从上面的“导入标记化”和“导入新”两个块开始的print 1。等等)
布赖恩2010年

3
Python3似乎不允许这样做,尽管不一定是故意的。我收到BOM错误。
东武

请注意,它import使用了内置功能__import__,因此,如果您覆盖了内置功能(导入需要修改后的导入的模块之前),则不需要单独的操作myimport
Tobias Kienzler 2016年

21

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

4
不过,这并不是真正的新语法……看起来只是这样。
汉斯·诺瓦克

3
-1:链接的页面具有以下标题:“'goto'模块是愚人节的一个玩笑,于2004年4月1日发布。是的,它有用,但仍然是个玩笑。请不要在实际代码中使用它!”
2013年

6
@Jim可能会重新考虑-1。它向您暗示了实现机制。很好的开始。
n611x007 2014年

14

缺少更改和重新编译源代码(在开放源代码中可能的)的情况下,更改基本语言实际上是不可能的。

即使您确实重新编译了源代码,也不会是python,只是经过修改的修改过的版本,您需要非常小心,不要引入错误。

但是,我不确定您为什么要这么做。Python的面向对象功能使使用这种语言实现类似的结果非常简单。


2
我不同意这一点。如果添加新的关键字,我认为它仍然是Python。如果您更改现有的关键字,那就像您所说的那样。
比尔蜥蜴

9
如果添加新的关键字,它将是Python衍生的语言。如果更改关键字,则它将是与Python不兼容的语言。
tzot

1
如果添加关键字,则可能缺少“简单易学的语法”和“扩展库”的要点。我认为语言功能几乎总是一个错误(示例包括COBOL,Perl和PHP)。
S.Lott

5
新关键字将破坏使用它们作为标识符的Python代码。
akaihola

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中嵌入整个语言,没有办法解决完整的语法方法。


11

这是一种仅在解释模式下添加新语句的非常简单但糟糕的方法。我只使用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

4

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

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

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

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


与PDF的真正链接-“ autonversion”已损坏,并且已经为上帝所知而中断了很长时间: troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

使用EasyExtend可以做到这一点

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


1
现在,该链接后面会出现一个页面:“ EasyExtend已死。对于那些对EE感兴趣的人,有一个后续项目,名为Langscape Different name,完整的重新设计,同样的过程。” 由于存在该信息页可能失效的危险,因此更新答案可能是一个好主意。
celtschk '16


1

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



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,那么修改语法并不难,我也怀疑是否已更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.