私は pyparsing を使用して DSL を構築することに取り組んでおり、素晴らしい進歩を遂げています。私の最初のマイルストーンは、算術演算子、データベース フィールド参照、一連の関数 (Avg、Stdev など) を含む式を評価することでした。さらに、モジュラーな方法で複雑な式を構築できるように、変数への式の代入を実装しました。ここまでは順調ですね。
引数として変数の関数を計算しようとすると、次の大きな障害にぶつかりました。具体的には、データベース参照 (計算が実行されるビルディング ブロック) では、クエリのディメンションとして Person を指定する必要があります。これらの変数が関数内に含まれている場合に、これらの変数に割り当てられた式の再評価を強制する最善の方法がわかりません。問題のある具体的な例:
1) CustomAvg = Avg[Height] + Avg[Weight]
2) Avg[CustomAvg]
CustomAvg が定数値に解決されているため、ステートメント 2 の評価は、People のリスト全体で期待どおりに機能しません。
これらのシナリオでは、CustomAvg のコンポーネントを計算するために反復処理する People のリストがあります。ただし、 Avg[CustomAvg] を評価すると、 CustomAvg の値は評価されるのではなく、変数のルックアップ dict から取得されるため、事実上、定数値を反復処理しています。関数内で引数として使用される変数がルックアップ テーブルから取得されるのではなく再評価されるように、評価に「認識」を導入する最良の方法は何ですか? 合理化された関連コードは次のとおりです。
class EvalConstant(object):
var_ = {}
def __init__(self, tokens):
self.value = tokens[0]
def eval(self):
v = self.value
if self.var_.has_key(v):
return self.var_[v]
else:
return float(v)
class EvalDBref(object):
person_ = None
def __init__(self, tokens):
self.value = tokens[0]
def eval(self):
v = self.value
fieldRef = v.split(':')
source = fieldRef[0]
field = fieldRef[1]
rec = db[source].find_one({'Name' : self.person_}, { '_id' : 0, field : 1})
return rec[field]
class EvalFunction(object):
pop_ = {}
def __init__(self, tokens):
self.func_ = tokens.funcname
self.field_ = tokens.arg
self.pop_ = POPULATION
def eval(self):
v = self.field_.value
fieldRef = v.split(':')
source = fieldRef[0]
field = fieldRef[1]
val = self.field_.eval()
if self.func_ == 'ZS':
# If using zscore then fetch the field aggregates from stats
rec = db['Stats'].find_one({'_id' : field})
stdev = rec['value']['stddev']
avg = rec['value']['avg']
return (val - avg)/stdev
elif self.func_ == 'Ptile':
recs = list(db[source].find({'Name' : { '$in' : self.pop_}},{'_id' : 0, field : 1}))
recs = [r[field] for r in recs]
return percentileofscore(recs, val)
def assign_var(tokens):
ev = tokens.varvalue.eval()
EvalConstant.var_[tokens.varname] = ev
#--------------------
expr = Forward()
chars = Word(alphanums + "_-/")
integer = Word(nums)
real = Combine(Word(nums) + "." + Word(nums))
var = Word(alphas)
assign = var("varname") + "=" + expr("varvalue")
assign.setParseAction(assign_var)
dbRef = Combine(chars + OneOrMore(":") + chars)
dbRef.setParseAction(EvalDBref)
funcNames = Keyword("ZS") | Keyword("Avg") | Keyword("Stdev")
functionCall = funcNames("funcname") + "[" + expr("arg") + "]"
functionCall.setParseAction(EvalFunction)
operand = dbRef | functionCall | (real | integer| var).setParseAction(EvalConstant)
signop = oneOf('+ -')
multop = oneOf('* /')
plusop = oneOf('+ -')
expr << operatorPrecedence(operand,
[
(signop, 1, opAssoc.RIGHT, EvalSignOp),
(multop, 2, opAssoc.LEFT, EvalMultOp),
(plusop, 2, opAssoc.LEFT, EvalAddOp),
])
EvalDBref.person_ = ‘John Smith’
ret = (assign | expr).parseString(line)[0]
str(ret.eval())