【问题标题】:Why python eval() can access imported functions, but not classes为什么 python eval() 可以访问导入的函数,但不能访问类
【发布时间】:2020-12-07 17:37:18
【问题描述】:

我有一个定义类和函数的模块:

#barbaz.py
class Bar:
    pass

def baz():
    return "hello"

在另一个模块中,我有一个带有 eval 语句的函数:

#foo.py (version 1)
def foo(baz: callable):
    bazstr: str = baz.__name__
    print(bazstr)
    try:
        f=eval(f'{bazstr}()')
        print(f'result: {f}')
    except Exception as e:
        print(f"eval error: {e}")

最后,我在另一个模块中进行了测试:

#test.py
from evalf import foo
from barbaz import baz, Bar

#This works...
foo(baz)
#This doesn't
foo(Bar)

baz 有效,但 Bar 无效。我得到输出:

baz
result: hello
Bar
eval error: name 'Bar' is not defined

似乎eval 不能使用从模块中导入的类,除非它直接导入到与foo() 函数相同的模块中。即,这有效:

#foo.py (version 2)
from barbaz import Bar

def foo(baz: callable):
    bazstr: str = baz.__name__
    print(bazstr)
    try:
        f=eval(f'{bazstr}()')
        print(f'result: {f}')
    except Exception as e:
        print(f"eval error: {e}")

foo(Bar)

为什么 foo.py 的第 2 版可以工作,而第 1 版会抛出显示的错误?

我怎样才能解决这个问题,并在它自己的模块中的 eval 语句中使用导入的类?

【问题讨论】:

    标签: python python-3.x scope eval


    【解决方案1】:

    foo.py 无法按预期工作,因为它在全局命名空间中没有 either 函数(eval 看起来对于在本地范围内找不到的名称)。它适用于函数,因为完全巧合的是,外部函数 (baz) 和参数名称 (baz) 是相同的。它根本没有看到def baz 函数,它看到了你命名的参数(巧合)baz

    解决办法就是无条件地使用你收到的参数的名字;如果您必须使用eval,只需将其中的可调用名称硬编码为baz,因为这总是在您的本地范围内被调用。您可能不应该使用 eval(在您的代码中,f = baz() 可以很好地完成这项工作),但如果您必须这样做,那就是解决方案。

    【讨论】:

    • >foo.py doesn't work as expected because it doesn't have either the class or the function in the global namespace。是否可以将函数或类添加到全局命名空间?或者,我可以使用 seccond 参数手动将其传递到 eval 命名空间,即类似于 eval("code", {'Baz' : Baz})
    • @CraigHickman:您绝对不希望 foo 在每次调用时都为了这样做而污染自己的全局命名空间。如果eval 是绝对必须,您可以将dicts 显式传递给globalslocals 参数到eval,这样您就可以在eval 的范围(确保你包含了你所依赖的实际全局/本地的任何东西)。坚持evalsmells of an XY problem,因为在提供的代码中不需要它,但是是的,如果你需要的话,你会这样做。
    【解决方案2】:

    问题是您的foo 函数中有baz,而没有Bar

    def foo(baz: callable):
        bazstr: str = baz.__name__
        print(bazstr)
        try:
            f = eval(f'{bazstr}()')
            print(f'result: {f}')
        except Exception as e:
            print(f"eval error: {e}")
    

    使用eval is dangerous,您只需在可调用参数旁边添加()。我将您的三个文件中的代码合二为一,因为单独的文件不会改变它的作用:

    class Bar:
        pass
    
    def baz():
        return "hello"
    
    def foo(baz: callable):
        try:
            f = baz()
            print(f'result: {f}')
        except Exception as e:
            print("Not callable.")
    
    foo(baz)
    foo(Bar)
    

    输出:

    result: hello
    result: <__main__.Bar object at 0x0000020EA3496E48>
    

    【讨论】:

    • >The problem is that you have baz in your foo function, and no Bar. 这只是函数参数的名称。我已经对其进行了编辑以使其更清晰
    • >Using eval is dangerous。我知道,但在这种情况下,我真的需要它。问题中的示例只是问题的最小表示。
    • @CraigHickman:不,这不是“只是参数的名称”;为什么一个电话有效而另一个电话无效,这一点至关重要。当您将参数名称更改为bar 时,您的调用的两个 版本都不起作用(因为现在bazBar 在本地或全局范围内都不可见)。您编辑的代码不再重现您声称看到的行为(并且仍然有错字;您声称它是 foo.py,但您从 evalf 导入)。
    猜你喜欢
    • 2018-01-28
    • 2019-11-17
    • 2013-06-26
    • 2017-04-15
    • 2017-09-24
    • 1970-01-01
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多