Pythonの構文に新しいステートメントを追加できますか?


124

新しい文(などを追加することができprintraisewithPythonの構文に)?

許可してください。

mystatement "Something"

または、

new_if True:
    print "example"

必要な場合はそれほどではありません、可能であれば(Pythonインタープリターのコードを変更しない限り)


10
多少関連する注意点として、その場で新しいステートメントを作成すると便利な(ユースケースは、言語を真剣に「拡張する」のではなく)、インタラクティブなインタプリタを計算機やOSシェルとして使用する人向けです。 。私は頻繁に、繰り返し実行する何かを実行するために、使い捨ての小さな関数を頻繁に作成します。そのような状況では、長い名前をfunction()構文で入力するのではなく、マクロやステートメントなどの非常に省略されたコマンドを作成する方がいいでしょう。もちろん、それはPyの目的ではありません。しかし、人々はそれをインタラクティブに使用することに多くの時間を費やしています。
Kilo

5
@Kiloそれはipythonを見る価値があるかもしれません-それは多くのシェルっぽい機能を持っています、例えばあなたは通常の "ls"と "cd"コマンド、タブ補完、たくさんのマクロ
っぽい

ForthやSmalltalkなど、一部の言語は非常に拡張可能ですが、それらの言語パラダイムはPythonでも使用されているものとは異なります。これらの両方で、新しい単語(Forth)またはメソッド(Smalltalk)は、そのインストールの言語の統合された、区別がつかない部分になります。したがって、ForthまたはSmalltalkの各インストールは、時間の経過とともに固有の作成になります。また、ForthはRPNベースです。しかし、DSLの考え方に沿って考えると、このようなことはPythonで実現できるはずです。しかし、他の人がここで言ったように、なぜですか?

1
PythonとForthの両方に堪能で、数年前にいくつかのForthコンパイラーを実装した人として、ある程度の権限を持ってここに貢献できます。Pythonの内部パーサーに直接アクセスしないと、完全に不可能です。以下の(率直に言うと、滑らかな!)回答が示すように、前処理によってそれを偽造できますが、ホットインタープリターで言語の構文やセマンティクスを本当に更新することはできません。これはPythonの呪いであり、LispやForthのような言語よりも優れている点でもあります。
Samuel A. Falvo II 2013

回答:


153

これが便利な場合があります-Python内部:Pythonに新しいステートメントを追加します。


この記事は、Pythonのフロントエンドがどのように機能するかをよりよく理解するための試みです。ドキュメントとソースコードを読むだけでは少し退屈な場合があるので、ここでは実践的なアプローチをとります。Pythonにuntilステートメントを追加します。

この記事のすべてのコーディングは、Python Mercurialレポジトリミラーの最先端のPy3kブランチに対して行われました。

until声明

Rubyなどの一部の言語に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

言語擁護余談

この記事はuntil、Pythonにステートメントを追加することを提案するものではありません。そのようなステートメントによってコードがより明確になると思いますが、この記事では追加がいかに簡単かを示していますが、Pythonのミニマリズムの哲学を完全に尊重しています。私がここでやろうとしているのは、実際には、Pythonの内部の仕組みを理解することです。

文法の変更

Pythonはという名前のカスタムパーサージェネレーターを使用しますpgen。これは、Pythonソースコードを解析ツリーに変換するLL(1)パーサーです。パーサジェネレータへの入力はファイルGrammar/Grammar[1]です。これは、Pythonの文法を指定する単純なテキストファイルです。

[1]:これ以降、Pythonソース内のファイルへの参照は、ソースツリーのルート(Pythonを構築するためにconfigureとmakeを実行するディレクトリ)に対して相対的に与えられます。

文法ファイルに2つの変更を加える必要があります。1つ目は、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にうまく適合しないためです)。

2番目の変更は、上記のスニペットにあるように、ルールをcompound_stmtを含むように変更するuntil_stmtことです。while_stmt再びの直後です。

make変更した後に実行するGrammar/Grammarと、pgenプログラムが実行されてとが再生成さInclude/graminit.hPython/graminit.c、その後、いくつかのファイルが再コンパイルされます。

AST生成コードの変更

Pythonパーサーが解析ツリーを作成した後、このツリーはASTに変換されます。これは、AST がコンパイルプロセスの後続の段階で処理するのはるかに簡単だからです。

それでは、Parser/Python.asdlPythonの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)、プログラミングを簡略化するためにミニ言語(つまりDSL)を使用するPythonソースコードの別の例です。また、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_stmtuntil私はこのelse条項をサポートしないことにしました。予想どおり、ASTはast_for_expr、条件式やステートメントのast_for_suite本文などの他のAST作成関数を使用して再帰的に作成されuntilます。最後に、という名前の新しいノードUntilが返されます。

となどのnマクロを使用して解析ツリーノードにアクセスすることに注意してください。これらは理解する価値があります。コードはにあります。NCHCHILDInclude/node.h

余談:AST構成

untilステートメントに新しいタイプのASTを作成することを選択しましたが、実際にはこれは必要ありません。いくつかの作業を保存し、既存のASTノードの構成を使用して新しい機能を実装することができました。

until condition:
   # do stuff

機能的には以下と同等です。

while not condition:
  # do stuff

Untilノードを作成する代わりに、ノードを子として持つノードをast_for_until_stmt作成することもできます。ASTコンパイラーはこれらのノードの処理方法をすでに知っているので、プロセスの次のステップはスキップできます。NotWhile

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_stmtgrepソースツリーにpingを何回実行しても、それが呼び出された場所が明らかになることはありません。この場合、1つのオプション、Cマクロfuのみが残ります。実際、簡単な調査により、次のようにVISIT定義されたマクロにつながりますPython/compile.c

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

での呼び出しcompiler_visit_stmtに使用されcompiler_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はスタックベースであり、Pythonバイトコードのリストと説明が記載さdisれているモジュールのドキュメントをざっと見て、何が起こっているのかを理解することができます。

これで完了です...私たちではないですか?

すべての変更を加えてを実行し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'

おっと…これは良くない。それで何が悪かったのですか?

不足しているシンボルテーブルのケース

ASTのコンパイル時にPythonコンパイラが実行する手順の1つは、コンパイルするコードのシンボルテーブルを作成することです。呼び出しPySymtable_BuildPyAST_Compileシンボルテーブルモジュール(への呼び出しPython/symtable.cコード生成機能と同様にASTを歩きます)。各スコープにシンボルテーブルがあると、コンパイラーは、どの変数がグローバルで、どの変数がスコープに対してローカルであるかなど、いくつかの重要な情報を理解するのに役立ちます。

この問題を修正するには、のsymtable_visit_stmt関数を変更して、ステートメントPython/symtable.cを処理するためのuntilコードをwhileステートメントの同様のコードの後に追加する必要があります[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列挙値がのswitchステートメントで処理されないことに気付きsymtable_visit_stmt、文句を言います。コンパイラの警告を確認することは常に重要です!

そして今、私たちは本当に終わりました。この変更後にソースをコンパイルすると、myfoo(3)期待どおりに作業が実行されます。

結論

この記事では、Pythonに新しいステートメントを追加する方法を示しました。Pythonコンパイラーのコードにはかなり手を加える必要がありますが、同様の既存のステートメントをガイドラインとして使用したため、変更の実装は難しくありませんでした。

Pythonコンパイラーは洗練された一連のソフトウェアであり、その専門家であるとは主張していません。ただし、私はPythonの内部、特にそのフロントエンドに本当に興味があります。したがって、この演習は、コンパイラの原理とソースコードの理論的研究に非常に役立つものであることがわかりました。これは、コンパイラーに深く入り込む将来の記事のベースとして機能します。

参考文献

この記事の構成には、いくつかの優れたリファレンスを使用しました。ここに、それらは順不同です:

  • PEP 339:CPythonコンパイラーの設計 -おそらくPythonコンパイラーの公式ドキュメントの最も重要で包括的なドキュメントです。非常に短いため、Pythonの内部についての優れたドキュメントの不足が痛々しく表示されます。
  • "Python Compiler Internals"-Thomas Leeによる記事
  • 「Python:設計と実装」-Guido van Rossumによるプレゼンテーション
  • Python(2.5)仮想マシン、ガイド付きツアー-PeterTrögerによるプレゼンテーション

元のソース


7
素晴らしい記事(/ブログ)、ありがとう!これは完全に質問に回答し、「それをしないでください」/「コーディング:mylang」の回答はすでに非常に賛成されているので受け入れます。したがって、順序どおりに表示されます\ o /
dbr

1
しかし、残念ながら、これは答えではありません。リンクされた記事はありますが、賛成投票も承認もできません。完全にリンクのみで構成される回答はお勧めしません。
Alfe

6
@Alfe:これは2年前に投稿され、16人の読者によって受け入れられ、+ 1されました。これは私自身のブログ投稿にリンクしており、大きな記事をStackOverflowにコピーすることは、私がやろうとしていることではないことに注意してください。警察を演じるのではなく、便利な編集でそれを自由にしてください。
Eli Bendersky 2013

2
@EliBendersky Usefulは、この記事ではかなり控えめな表現です。これらのものがPythonで実際にどのように機能するかについて説明していただきありがとうございます。これは、現在の作業に関連するASTを理解するのに本当に役立ちました。**また、興味がある場合は、私のバージョンのuntilis isa/ isanas if something isa dict:またはif something isan int:
as

5
スー、この答えは「Pythonから分岐したソースから独自の言語を
記述して

53

このようなことを行う1つの方法は、ソースを前処理して変更し、追加したステートメントを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から使用できるようにする方法です。1つの方法は、独自のインポート関数を記述し、それを使用してカスタム言語で記述されたコードをロードすることです。つまり:

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が見るのは前処理されたファイルです。つまり、スタックトレースなどに出力されるテキストはそれを参照します。重要な翻訳を行った場合、これは原文とは大きく異なる場合があります。上記の例では、行番号などは変更されないため、それほど大きな違いはありませんが、変更するほど、把握が難しくなります。


12
良いですね!「できません」と言う代わりに、実際にはいくつかの良い答えを出します(つまり、「本当にこれをしたくない」ということになります)。
c0m4 2008年

最初の例がどのように機能するかがわかりません。コード行myimportだけが生成さprint 1れるため、単純に含まれているモジュールで使用しようとしています=1 ... SyntaxError: invalid syntax
olamundo

@noam:何が問題なのかわからない-ここでは、期待どおり「1」が印刷されます。(これは、上記の "import tokenize"と "import new"で始まる2つのブロックが、ファイル "。b=myimport("b.py")"と" "、および "。print 1 "。。エラーには他に何かあります(スタックトレースetc)?
ブライアン

3
Python3はこれを許可していないようですが、必ずしも意図的ではありません。BOMエラーが発生します。
東武

importは組み込みを使用している__import__ため、それを上書きする場合(変更されたインポートを必要とするモジュールをインポートする前に)は不要ですmyimport
Tobias Kienzler

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日に公開されたエイプリルフールのジョークでした。はい、動作しますが、それでもジョークです。実際のコードでは使用しないでください!」
ジム

6
@ジムは-1を再考するかもしれません。実装メカニズムについてヒントを与えてくれます。まずはいいことです。
n611x007 14

14

ソースコードの変更と再コンパイル(これオープンソースで可能で、ベース言語の変更は実際には不可能です。

ソースを再コンパイルしても、それはpythonではなく、バグを持ち込まないように細心の注意を払う必要のある変更されたバージョンです。

しかし、なぜあなたがしたいのかはわかりません。Pythonのオブジェクト指向機能により、現状の言語で同様の結果を簡単に実現できます。


2
私は一点で同意しません。あなたがいる場合に追加の新しいキーワードが、私はそれがまだPythonのことだと思います。あなた既存のキーワードを変更するならば、あなたが言うように、それはただハッキングされただけです。
トカゲに請求する

9
新しいキーワードを追加すると、それはPython派生言語になります。キーワードを変更すると、Pythonと互換性のない言語になります。
tzot 2008年

1
キーワードを追加すると、「簡単に学習できる構文」と「拡張ライブラリ」のポイントを逃してしまう可能性があります。言語機能はほとんど間違いだと思います(例としては、COBOL、Perl、PHPなど)。
S.Lott、2008年

5
新しいキーワードは、それらを識別子として使用するPythonコードを破壊します。
akaihola 2009年

12

一般的な答え:ソースファイルを前処理する必要があります。

より具体的な答え: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にcdしてタイプします

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に言語全体を埋め込むなどのより複雑なことについては、完全な文法アプローチを回避する方法はありません。


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を再コンパイルする必要はありません。)


:「autonversion」が壊れていると、今長い神ノウハウのために切断されたこと- PDFへの実際のリンク troeger.eu/files/teaching/pythonvm08lab.pdf
ZXX

3

EasyExtendを使用してこれを行うことができます。

EasyExtend(EE)は、純粋なPythonで記述され、CPythonと統合されたプリプロセッサジェネレーターおよびメタプログラミングフレームワークです。EasyExtendの主な目的は、拡張言語の作成、つまりカスタム構文とセマンティクスをPythonに追加することです。


1
そのリンクをたどると、「EasyExtendは機能しなくなりました。EEに興味がある人のために、Langscapeの別の名前の後継プロジェクトがあり、完全な再設計、同じ旅をしています。」この情報ページが機能しなくなる可能性があるため、回答を更新することをお勧めします。
セルチク


1

インタープリターを変更せずに。過去数年の多くの言語が「拡張可能」であると説明されてきましたが、あなたが説明している方法ではそうではありません。関数とクラスを追加してPythonを拡張します。


1

そのようなことができるLogixと呼ばれる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

10年前はそれができませんでしたが、それが変わったとは思えません。ただし、Pythonを再コンパイルする準備ができていれば、構文を変更することはそれほど難しくありませんでした。それも変更されたとは思えません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.