如何在模块中解析全局变量?

4 投票
2 回答
3599 浏览
提问于 2025-04-17 15:39

我有一个脚本,内容如下:

from mapper import Mapper

class A(object):
    def foo(self):
        print "world"

a = A()
a.foo()
Mapper['test']()

其中Mapper是在文件mapper.py中定义的:

Mapper = {'test': a.foo}

我想在这里定义一个函数调用,引用一个在mapper.py中没有定义的对象,而是在原始代码中定义的。不过,上面的代码出现了错误:

NameError: name 'a' is not defined

这个错误有点道理,因为amapper.py里并没有定义。不过,有没有办法修改代码,让它能够在主代码中解析这个名字,或者通过使用globals之类的方式来实现呢?

为了解决这个问题,我可以在mapper.py中将实现指定为文本,然后在主代码中使用eval,但我希望避免使用eval

附加信息:

  • 函数的完整定义必须在mapper.py中完成。
  • 事先不知道实例a是什么,或者它是从哪个类实例化的。

2 个回答

0

我不太确定我完全理解了,不过a可以在任何有Mapper引用的地方“注册”它的方法:

#mapping.py
Mapper = {}

然后:

#main.py
from mapping import Mapper

#snip
a = A()
Mapper['test'] = a.foo #put your instance method into the Mapper dict.
#snip

Mapper['test']()
1

除了像 eval 这样的安全漏洞之外,在 mapper.py 中使用名字 a 是不可能的,除非这个名字在 mapper.py 中定义过,或者从其他模块导入过。你不能让 mapper.py 自动、默默地从其他模块获取一个值 a

而且,如果你只是像你例子中那样在字典里使用它,a.foo 会在字典创建时立即被计算。它不会等到你真正调用这个函数时才计算;一旦它计算 a.foo 来创建字典,就会失败,因为它不知道 a 是什么。

你可以通过把这个元素放在一个函数里来解决第二个问题(用一个简短的 lambda 表达式):

Mapper = {'test': lambda: a.foo}

……但这仍然没用,除非你能以某种方式让 amapper.py 中可用。

一种可能的方法是通过“神秘”对象来参数化你的 Mapper,然后从外部传入这个对象:

# mapper.py
Mapper = {'test': lambda a: a.foo}

# other module
from mapper import Mapper
Mapper['test'](a)()

或者,像 mgilson 建议的那样,你可以以某种方式将对象 a “注册”到 Mapper 中。这样你只需传递一次对象 a 来注册它,之后就不需要在每次调用时都传递它:

# mapper.py
Mapper = {'test': lambda a: Mapper['a'].foo}

# other module
from mapper import Mapper
Mapper['a'] = a
Mapper['test']()()

注意最后的两组括号:一组用于计算 lambda 并提取你想调用的函数,另一组则是实际调用这个函数。你也可以通过使用模块级变量,而不是用 Mapper['a'] 作为引用,来做类似的事情:

# mapper.py
Mapper = {'test': lambda: a.foo}

# other module
import mapper
Mapper = mapper.Mapper

mapper.a = a
Mapper['test']()()

注意,这需要你在另一个模块中执行 import mapper 来设置模块变量。

你可以通过使用自定义类来代替普通字典来简化这个过程,让这个类在它的 __getitem__ 方法中做一些工作,去一个“已知位置”(例如,读取某个模块变量)来作为评估 a 的基础。不过,这会是一个更复杂的解决方案。

总之,你不能(再次强调,除非使用 eval 或其他类似的漏洞)在 mapper.py 中写代码使用一个未定义的变量 a,然后在另一个模块中定义这个变量 a,让 mapper.py 自动知道这个变量。必须有某行代码在某个地方“告诉” mapper.py 你希望它使用哪个 a 的值。

撰写回答