1

私は 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())
4

2 に答える 2

1

したがって、この式では:

CustomAvg = Avg[Height] + Avg[Weight]

HeightWeightすぐに評価されることになっCustomAvgていますが、将来のある時点で評価されることになっていますか?その場合、これは新しい定数ではなく、関数または呼び出し可能オブジェクトの定義に似ているように思われます。私はあなたがしなければならないのは何が起こるかを変えることだと思いますassign_var

def assign_var(tokens):
    # ev = tokens.varvalue.eval()
    # EvalConstant.var_[tokens.varname] = ev
    EvalConstant.var_[tokens.varname] = tokens.varvalue

これで、割り当てられたすべての変数が定数値ではなく、Pythonでラムダを作成するのと同様に、評価可能な式になります。次にEvalConstant.eval、値を返すことができるかどうか、または値自体を評価する必要があるかどうかを検出する必要があります。

def eval(self):
    v = self.value
    if v in self.var_:  # has_key is deprecated Python, use 'in'
        varval = self.var_[v]
        return varval.eval() if hasattr(varval,'eval') else varval
    else:
        return float(v)

常にこれが発生することを望まない場合は、定数を割り当てるときと、本質的にラムダであるものを定義するときを区別するために、新しい構文が必要になる可能性があります。

CustomAvg = Avg[Height] + Avg[Weight]    # store as a constant
CustomAvg *= Avg[Height] + Avg[Weight]   # store as a callable

そして、次のように変更assignします。

assign = var("varname") + oneOf("= *=")("assign_op") + expr("varvalue")

そして、次のようにassign_varなります。

def assign_var(tokens):
    if tokens.assign_op == '*=':
        # store expression to be eval'ed later
        EvalConstant.var_[tokens.varname] = tokens.varvalue
    else:
        # eval now and save result
        EvalConstant.var_[tokens.varname] = tokens.varvalue.eval()
于 2012-12-11T07:10:31.970 に答える
0

あなたの問題はスコープだと思います。関数への引数は、通常、ローカルと同じスコープ内にあると見なされます。したがって、ステートメントはローカルの現在の値CustomAvg = Avg[Height] + Avg[Weight] Avg[CustomAvg]をスタックにプッシュし、式を評価してから、結果を CustomAvg に格納する必要があります。(または、名前/値の Python ビューを使用している場合は、結果を指すように名前を設定します。)CustomAvgCustomAvg

値が eval スタックにプッシュされてからずっと後に割り当てが行われるため、あいまいさはありません。

于 2012-12-10T23:50:18.763 に答える