web-dev-qa-db-ja.com

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

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

言う、許可するために..

mystatement "Something"

または、

new_if True:
    print "example"

shouldではなく、可能であれば(pythonインタープリターコードの変更)

113
dbr

このようなことを行う1つの方法は、ソースを前処理して変更し、追加したステートメントをpythonに変換することです。このアプローチがもたらすさまざまな問題があり、一般的な使用にはお勧めしませんが、言語の実験や特定の目的のメタプログラミングには、時々役立つことがあります。

たとえば、画面に印刷する代わりに特定のファイルに記録する「myprint」ステートメントを導入したいとしましょう。すなわち:

_myprint "This gets logged to file"
_

と同等になります

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

構文を既存のpythonとどれだけ一致させるかに応じて、正規表現の置換からASTの生成、独自のパーサーの作成まで、置換の方法に関するさまざまなオプションがあります。適切な中間アプローチは、トークナイザーモジュールを使用することです。これにより、ソースを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モジュールとは異なる方法で処理する必要があります。つまり、「_import some_mod_」ではなく「some_mod = myimport("some_mod.py")

別のかなりきちんとした(ハッキーとはいえ)ソリューションは、 this レシピが示すように、カスタムエンコーディングを作成することです( PEP 26 を参照)。これを次のように実装できます。

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

51
Brian

はい、ある程度可能です。 sys.settrace()を使用してgotoおよびcomefromの「キーワード」を実装する module があります。

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"
20
Constantin

ソースコードの変更と再コンパイル(isはオープンソースで可能)を除いて、ベース言語の変更は実際には不可能です。

ソースを再コンパイルしたとしても、それはpythonではなく、ハッキングされたバージョンを変更しただけなので、バグを持ち込まないように非常に注意する必要があります。

しかし、なぜあなたがしたいのか分かりません。 Pythonのオブジェクト指向機能により、現在の言語で同様の結果を得ることが非常に簡単になります。

13
paxdiablo

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

より具体的な回答: 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はいくつかのバグを除けば非常に信頼できます。したがって、プログラマーが便利なOOプログラミングを使用して中置演算子や小さなステートメントなどの便利なものを定義できるAPIが登場するのは時間の問題です。ラングレットを構築してPythonに言語全体を埋め込むなど、より複雑なものについては、完全な文法アプローチを回避する方法はありません。

12
Kay Schluehr

以下に、新しいステートメントを追加する非常に簡単ですが、くだらない方法を示します解釈モードのみ。 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 __== '__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

10
jcomeau_ictx

新しいステートメントの追加に関するガイドを見つけました。

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

基本的に、新しいステートメントを追加するには、Python/ast.c(特に)とpythonバイナリを再コンパイルします。

可能ですが、できません。関数とクラスを使用してほとんどすべてを達成できます(スクリプトを実行するためにpythonを再コンパイルする必要はありません)。)

4
dbr

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

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

3
Matthew Trevor

言語構文に新しいステートメントを正確に追加するわけではありませんが、マクロは強力なツールです。 https://github.com/lihaoyi/macropy

2
peterhil

pythonと呼ばれる言語 Logix を使用して、そのようなことを実行できます。にとって 仕事する 最新バージョンで。

1
Claudiu

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

1
Bill the Lizard

デコレータでいくつかのことができます。例えば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.

また、デコレータは通常、関数を実行するのではなく変更することが期待されています。

以前は、いくつかの機能の作業ディレクトリを一時的に設定する必要があったスクリプトで、このような方法を使用していました。

1
kdb

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

0
Alex Coventry