その文字列をツリー (または同等に解釈可能なデータ構造) に一度だけ解析し、関数を繰り返し使用して、対象の変数割り当てセットごとに「ツリーを解釈」します。(「解釈可能なデータ構造」として Python バイトコードを生成することもできるためeval
、「ツリーの解釈」として使用できます。これにより、生成が遅くなりますが、必要なのは 1 回だけであり、解釈も高速です)。
あなたが言うように、それは少し抽象的なので、単純すぎる場合は具体的な例を挙げましょう。たとえば、変数が x、y、z、t の文字であり、演算子が加算の場合は a で、減算の場合は s であるとします。隣接する文字の文字列は、一般的な数学の慣例のように、暗黙のうちに優先順位の高い乗算を意味します。括弧なし、厳密な左から右への実行 (つまり、演算子の優先順位なし、乗算を超える)。これらの 6 文字を除くすべての文字は無視する必要があります。次に、非常にアドホックなパーサーと Python バイトコード ジェネレーターを次に示します。
class BadString(Exception): pass
def makeBytecode(thestring):
theoperator = dict(a='+', s='-')
python = []
state = 'start'
for i, letter in enumerate(thestring):
if letter in 'xyzt':
if state == 'start':
python.append(letter)
state = 'variable'
elif state == 'variable':
python.append('*')
python.append(letter)
elif letter in 'as':
if state == 'start':
raise BadString(
'Unexpected operator %r at column %d' % (letter, i))
python.append(theoperator[letter])
state = 'start'
if state != 'variable':
raise BadString(
'Unexpected operator %r at end of string' % letter)
python = ''.join(python)
# sanity check
# print 'Python for %r is %r' % (thestring, python)
return compile(python, thestring, 'eval')
eval
これで、この結果を最初の引数として呼び出し、値を x、y、z、および t に関連付ける辞書を 2 番目の引数として単純に呼び出すことができます。例 (上記のモジュールを as としてインポートしpar
、健全性チェックのコメントを外した場合):
>>> c=par.makeBytecode('xyax')
Python for 'xyax' is 'x*y+x'
>>> for x in range(4):
... for y in range(5):
... print 'x=%s, y=%s: result=%s' % (x,y,eval(c,dict(x=x,y=y)))
...
x=0, y=0: result=0
x=0, y=1: result=0
x=0, y=2: result=0
x=0, y=3: result=0
x=0, y=4: result=0
x=1, y=0: result=1
x=1, y=1: result=2
x=1, y=2: result=3
x=1, y=3: result=4
x=1, y=4: result=5
x=2, y=0: result=2
x=2, y=1: result=4
x=2, y=2: result=6
x=2, y=3: result=8
x=2, y=4: result=10
x=3, y=0: result=3
x=3, y=1: result=6
x=3, y=2: result=9
x=3, y=3: result=12
x=3, y=4: result=15
>>>
より洗練されたシンプルな文字列の解析と、迅速に解釈可能なデータ構造の構築については、たとえばpyparsingを参照してください。