1

我一直在努力用 pyparsing 构建我的 DSL,并取得了很好的进展。我的第一个里程碑是评估包含算术运算符、数据库字段引用和一组函数(Avg、Stdev 等)的表达式。此外,我还实现了将表达式分配给变量,以便能够以模块化的方式构建复杂的表达式。到现在为止还挺好。

在尝试将变量作为参数计算函数时,我现在遇到了下一个主要障碍。具体来说,我的数据库引用(这是执行计算的构建块)需要将 Person 指定为查询的维度。当它们包含在函数中时,我不知道强制重新评估分配给这些变量的表达式的最佳方法。有问题的具体例子:

1) CustomAvg = Avg[Height] + Avg[Weight]
2) Avg[CustomAvg]

评估语句 2 在人员列表中无法按预期工作,因为 CustomAvg 被解析为一个常量值。

在这些场景中,我有一个人员列表,我遍历这些人员以计算 CustomAvg 的组件。但是,当我评估 Avg[CustomAvg] 时,CustomAvg 的值来自我的变量查找字典而不是被评估,因此我有效地迭代了一个常量值。在我的评估中引入“意识”的最佳方法是什么,以便重新评估函数中用作参数的变量而不是从查找表中获取?以下是精简的相关代码:

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]

Height并且Weight应该立即进行评估,但CustomAvg应该在将来的某个时间进行评估?听起来这更像是函数或可调用的定义,而不是新常量的定义。我认为你所要做的就是改变发生的事情assign_var

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

现在每个分配的变量都不是一个常量值,而是一个 eval'able 表达式,类似于在 Python 中创建一个 lambda。然后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)

如果您不总是希望这种情况发生,那么我认为您可能需要一些新语法来区分何时分配常量与定义本质上是 lambda 的内容,可能类似于:

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压入堆栈,计算表达式,然后将结果存储到 CustomAvg。(或者CustomAvg,如果您使用的是名称/值的 Pythonic 视图,则将名称设置为指向结果。)

由于赋值是在值被压入 eval 堆栈之后很久才发生的,因此不应该有任何歧义。

于 2012-12-10T23:50:18.763 回答