您如何仅在对象上下文中执行python'eval'?

问题描述 投票:9回答:4

是否有可能做类似的事情

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

在Python中。即是否在对象“ c”的范围内对func1()和func2()进行了评估(如果它们是该类定义内的成员函数)?我无法进行简单的解析,因为对于我的应用程序,评估字符串可能会变得任意复杂。我猜想用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'))    
python eval abstract-syntax-tree
4个回答
10
投票

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

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

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

self

[请记住,>>> class Foo(object): ... def __init__(self): ... self.bar = 3 >>> foo = Foo() >>> eval('bar', globals(), foo.__dict__) 3 可能与您想要的不完全一样。例如:

__dict__

为了按照您想要的方式进行工作,您必须完全了解如何用Python术语定义所需的内容-这需要了解一些对象在幕后的工作方式(MRO,也许是描述符等)。

如果您确实需要>>> 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 ,并且确实需要提供任意上下文,则最好是显式地构建这些上下文(作为字典),而不是尝试将对象强制充当该角色:

eval

这种用法与您试图在Python中模仿的Javascript风格非常接近。

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

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

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


1
投票

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

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

似乎您需要的。让我解释一下它是如何工作的。

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

我希望这会有所帮助。


1
投票

上面建议的填充eval的解决方案在大多数情况下效果很好,但在属性(数据描述符)的情况下可能会出现问题。填充字典时,对它们进行评估。这意味着对同一变量名称的多个引用将始终返回完全相同的实例,对于属性,这可能不是预期的行为。此问题可以通过注意到locals期望eval自变量

behaves

locals来解决(与全局变量相对,它必须be一个dict)。换句话说,我们可以覆盖实例中的dict以在实例的上下文中即时解析变量名,并将其作为__getitem__属性直接传递给locals。您的示例可以这样实现:eval

此技巧应与继承,静态方法,类方法和属性一起使用。最后,尽管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()
的结果可能是dir,但使用getattr__dict__避免了直接与__mro__dir交互的需要。

您可以查看对此问题的公认答案:not always be complete

这对我创建自己的上下文是一种有用的方式,在这种上下文中,对矩形数组(例如Python Pandas数据框)的数学运算可以“正常工作”,而无需理会丑陋的Pandas语法。例如,当我在上下文中写入“ "Getting the block of commands that are to be executed in the with-statement"”时,它会自动将a = x*y分配为上下文对象的属性,并且知道使用上下文对象的ax属性执行矢量运算。

尽管我每次在StackOverflow上询问时,都会经常得到古怪的回答,但这不一定是我真正想做的。

您可能也可以在y也在其中寻找功能的情况下使用此功能。


0
投票
您可以查看对此问题的公认答案:not always be complete
© www.soinside.com 2019 - 2024. All rights reserved.