12

是否可以做类似的事情

c = MyObj()
c.eval("func1(42)+func2(24)")

在 Python..ie 中有 func1() 和 func2() 在对象“c”的范围内进行评估(如果它们是该类定义中的成员函数)?我无法进行简单的解析,因为对于我的应用程序,eval 字符串可能会变得任意复杂。我想用 ast 模块做一些魔术可能会奏效,但由于关于 ast 的文献很脏,我不确定在哪里看:

import ast

class MyTransformer(ast.NodeTransformer):
    def visit_Name(self, node):
        # do a generic_visit so that child nodes are processed
        ast.NodeVisitor.generic_visit(self, node)
        return ast.copy_location(
            # do something magical with names that are functions, so that they become 
            # method calls to a Formula object
            newnode,
            node
        )

class Formula(object):

    def LEFT(self, s, n):
        return s[:n]

    def RIGHT(self, s, n):
        return s[0-n:]

    def CONCAT(self, *args, **kwargs):
        return ''.join([arg for arg in args])

def main():

    evalString = "CONCAT(LEFT('Hello', 2), RIGHT('World', 3))"

    # we want to emulate something like Formula().eval(evalString)
    node = ast.parse(evalString, mode='eval')
    MyTransformer().visit(node)

    ast.fix_missing_locations(node)
    print eval(compile(node, '<string>', mode='eval'))    
4

4 回答 4

12

你几乎肯定不想这样做,但你可以

的上下文eval是您要在其中评估代码的全局和局部字典。最常见的情况可能是eval(expr, globals(), mycontext)eval(expr, mycontext),它们分别替换了默认的局部和全局上下文,而另一个则不用管。用对象的字典替换本地上下文类似于运行该对象的“内部”(一种方法)——尽管请记住,如果您不这样做,“作为成员函数”并没有您预期的那么好必须self调用其他成员函数…</p>

无论如何,这是一个简单的例子:

>>> class Foo(object):
...     def __init__(self):
...         self.bar = 3
>>> foo = Foo()
>>> eval('bar', globals(), foo.__dict__)
3

请记住,这__dict__可能不是您想要的。例如:

>>> class Foo(object):
...     @staticmethod
...     def bar():
...         return 3
>>> foo = Foo()
>>> eval('bar()', globals(), foo.__dict__)
NameError: name 'bar' is not defined
>>> eval('bar()', globals(), {k: getattr(foo, k) for k in dir(foo)}
3

为了使这项工作以您想要的方式工作,您必须确切地知道如何定义您想要的,用 Python 术语 - 这需要了解对象如何在幕后工作(MRO,也许是描述符等)。

如果您确实需要eval,并且确实需要提供任意上下文,那么您最好明确地构建这些上下文(作为字典),而不是试图强制对象进入该角色:

>>> foo = {
...     'bar': lambda: 3
... }
>>> eval('bar()', globals(), foo)

无论如何,这种用法更接近于您尝试在 Python 中模拟的 Javascript 样式。

当然,与 JS 不同,Python 不允许您将多行定义放在表达式中,因此对于复杂的情况,您必须这样做:

>>> def bar():
...     return 3
>>> foo = {
...     'bar': bar
... }
>>> eval('bar()', globals(), foo)

但可以说这几乎总是更具可读性(这基本上是 Python 背后的论点,不允许表达式中的多行定义)。

于 2012-12-17T22:39:36.190 回答
2

所以,我建议你做这样的事情:

>>> class S(object):
...     def add(self, a, b):
...         return a + b
... 
>>> filter(lambda (k,v): not k.startswith("__"), S.__dict__.items())
[('add', <function add at 0x109cec500>)]
>>> target = S()
>>> map(lambda (k, f): (k, f.__get__(target, S)), filter(lambda (k,v): not k.startswith("__"), S.__dict__.items()))
[('add', <bound method S.add of <__main__.S object at 0x109ce4ed0>>)]
>>> dict(_)
{'add': <bound method S.add of <__main__.S object at 0x109ce4ed0>>}
>>> eval("add(45, 10) + add(10, 1)", _, {})
66

好像是你需要的。让我解释一下这是如何工作的。

  1. eval接受局部变量和全局变量作为参数。
  2. 所以我们需要定义特殊的全局上下文,它将成为你的类的“表示”。
  3. 为此,我们需要提供globals所有“有价值的”有界方法的字典。
  4. 从简单部分开始。我们有S类定义。如何获得所有“有价值”的方法?简单的filter名称 fromS.__dict__为了检查方法名称是否从开始__(你看,结果我们得到一个包含 1 项的列表 -add函数)。
  5. 创建target= 类的实例S,它将是“评估上下文”。
  6. 下一步是最“棘手”的。我们需要从每个函数中创建“绑定方法”。为此,我们使用了这些事实,即类__dict__存储函数,每个函数都是非数据描述符,并且可以简单地使用 获取有界方法func.__get__(obj, type(obj))。此操作在 中执行map
  7. 从上一步中获取结果,从中创建dict
  8. 传为globals函数eval

我希望这个能帮上忙。

于 2012-12-17T22:48:50.177 回答
1

上面提出的填充解决方案locals在大多数情况下效果很好,但在属性(数据描述符)的情况下可能会出现问题。当字典被填充时,这些被评估一次。这意味着对同一变量名的多次引用将始终返回完全相同的实例,这在属性的情况下可能不是预期的行为。

这个问题可以通过注意eval期望一个行为像 a的locals参数来解决(而不是全局变量,它必须a )。换句话说,我们可以在您的实例中覆盖以在实例的上下文中动态解析变量名称,并将其作为属性直接传递给. 因此,您的示例可以实现为:dictdict__getitem__localseval

class Formula(object):
    def __getitem__(self, key):
        if key not in dir(self) or key.startswith('__'):
            raise KeyError(key)
        return getattr(self, key)

    def LEFT(self, s, n):
        return s[:n]

    def RIGHT(self, s, n):
        return s[0-n:]

    def CONCAT(self, *args, **kwargs):
        return ''.join([arg for arg in args])


def main():
    evalString = "CONCAT(LEFT('Hello', 2), RIGHT('World', 3))"
    print eval(evalString, {}, Formula())

if __name__ == "__main__":
    main()

这个技巧应该适用于继承、静态方法、类方法和属性。最后,使用dirand避免了直接与orgetattr交互的需要,尽管结果可能并不总是完整的。__dict____mro__dir

于 2015-10-16T18:59:57.853 回答
0

您可能会看一下这个问题的公认答案:“Getting the block of commands that are to be executed in the with-statement”

这对我创建自己的上下文很有帮助,在这些上下文中,矩形数组(例如 Python Pandas 数据帧)上的数学运算“正常工作”,而无需为难看的额外 Pandas 语法而烦恼。例如,当我a = x*y在上下文中写入“”时,它会自动将属性分配给上下文对象,并且它知道要对上下文对象和属性a执行矢量操作。xy

我发现这个上下文内容非常有用,尽管每当我在 StackOverflow 上提问时,我经常会收到一些很糟糕的回答,说这一定不是我真正想做的事情。

您可能也可以eval让它适用于查找函数的上下文。

于 2012-12-17T22:46:11.957 回答