如何添加可以被Python编译器绑定的全局符号?

How to add a global symbol that can be bound by Python compiler?

这是我的第一个问题,请多多关照 :) 我是 Python 的新手,但我对其他编程语言(例如 C++)非常有经验。


更新 2 - 找到解决方案

感谢大家的帮助:) 由于解决方案在评论中 "hidden" 我会在这里重新发布。

而不是

file_symbols = {}

首先必须将变量 local_symbol 添加到 file_symbols 字典中:

file_symbols = { "local_symbol" : local_symbol }

对于阅读本文的任何人:此处发布的所有变量/class 名称都不应被理解为实际有用的名称,因为这些示例本质上是合成的 ;)


嗯...现在我必须弄清楚以下的完整含义:

exec compiled_code in file_symbols

到目前为止,我认为它只会用 compiled_code 中的符号更新字典 file_symbols。 但它实际上看起来更像一点! :)


更新 1

好的,我下面的示例项目似乎太简单了,无法显示实际问题。无论如何,感谢您已经提供的支持! :)

事实上,我想先编译多个需要访问本地符号(class 实例)的 *.py 文件。来自这些编译文件的所有符号都应收集起来,然后用作其他代码对象的环境。

所以我真的需要这样做 (注意以下代码显示的是概念,而不是实际的可执行代码):

class Functions:
    (...)

global_symbols = {}
local_symbol = Functions()

# note that the external files need to access local_symbol to call its functions!
for file in external_files:
    code = file.load()
    compiled_code = compile(code, "<string>", "exec")
    file_symbols = {}
    exec compiled_code in file_symbols
    global_symbols.update(file_symbols)

some_code = another_file.load()
compiled_code = compile(some_code, "<string>", "exec")
exec(compiled_code, global_symbols)

在此示例中,行

exec compiled_code in file_symbols

产生一个 NameError() - 因为他们无法访问 local_symbol 因为它在外部文件的任何地方都没有 定义 尽管它应该被使用!

所以问题是如何为 external_files 提供对 local_symbol 的访问权限,以便他们可以调用实例的函数??

我的导入挂钩解决方案,你们中的一些人认为 "hack" 是迄今为止唯一可行的解​​决方案。如果有的话,我很乐意使用更简单的!

再次感谢 :)


我最初的问题是:

我们开始吧。我打算做的是高级的东西,我在这里和其他地方都没有找到解决我的问题的方法。

假设Python中有如下代码(2.6.x / 2.7.x):

class Functions:
    def __init__(self):
        (...)
    def func_1(...):
        (...)
    def func_2(...):
        (...)
    (...)
    def func_n(...):
        (...)
functions = Functions()
code = loadSomeFile(...)
compiled_code = compile(code, "<string>", "exec")
(...)
global_env = {}
local_env = {"func":functions}
exec(compiled_code, global_env, local_env)

上面示例中的 code 是从一个文件加载的,其内容可能如下所示:

import something
(...)
def aFunction(...):
    a = func.func_1(...)
    b = func.func_2(...)
    return a * b
(...)
aFunction()

请注意,上面代码中的 (...) 意味着为了简单起见,我可能省略了更多代码。

我在示例中遇到的问题是编译器针对这一行引发错误:

compiled_code = compile(code, "<string>", "exec")

我会得到这个错误:NameError("global name 'func' is not defined")

这个错误是完全可以理解的,因为编译器无法绑定到名称为 "func" 的任何全局符号。但是我还是想这样编译代码。

所以显而易见的问题是:

如何定义编译器可用于 compile() 语句的全局符号,以便编译器将任何 "unknown" 符号绑定到我选择的对象?

在我的示例中,我想定义一个绑定到 class Functions 实例的全局符号 func,以便编译器在编译使用 [= 的代码时找到该符号25=] 如上例所示。

那么如何实现呢?

重要: 请注意,我知道直接使用 exec(...) 执行代码会解决编译问题,因为上面示例中的字典 local_env 会提供成功执行所需的符号。但是我不能这样做,因为要编译的代码一点也不小。它可能由数百行代码组成,而且这段代码也不是只执行一次,而是执行很多次。 所以出于性能原因我真的需要编译代码而不是直接执行它。

谢谢你帮助我:)

这是一个有趣的问题,感谢您发布。我一直在研究如何在编译时更改 globals table 。显然,您可以直接调用 __import__() 函数并且:

pass your globals in order to determine how to interpret the name in a package context.

来源:Package documentation

好吧,很明显,它只需将实例添加(并可能删除 - 如果使用多个 "func" 实例)到 sys.modules:

sys.modules["func"] = functions
(...)
compiled_code = compile(code, "<string>", "exec")

包含导入程序挂钩(拦截源代码文件中的 "import func" 行)的完整且有效的解决方案如下所示:

import sys

class Functions:
    def __init__(self):
        (...)
    def func_1(...):
        (...)
    def func_2(...):
        (...)
    (...)
    def func_n(...):
        (...)

functions = Functions()
code = loadSomeFile(...)

hook_name = "func"

class ImporterHook:
    def __init__(self, path):
        if path != hook_name:
            raise ImportError()
    def find_module(self, fullname, path=None):
        return self
    def load_module(self, path):
        if sys.modules.has_key(path):
            return sys.modules[path]
        else:
            sys.modules[path] = functions
            return functions

sys.path_hooks.append(ImporterHook)
sys.path.insert(0, hook_name)
compiled_code = compile(code, "<string>", "exec")
(...)
exec(compiled_code)

并不像看起来那么难:)有关更多信息,请参见此处:

https://www.python.org/dev/peps/pep-0302/#specification-part-2-registering-hooks

这里:

https://pymotw.com/2/sys/imports.html

谢谢 :)

不要向 exec 提供单独的 globalslocals 指令。这会导致执行的代码表现得就像嵌入在 class 定义中一样。这意味着 executed 代码中定义的函数中的任何变量查找绕过 locals

>>> exec("""
... def f():
...     print a
... f()""", {}, {"a": 3})
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<string>", line 4, in <module>
  File "<string>", line 3, in f
NameError: global name 'a' is not defined
>>> exec("""
... def f():
...     print a
... f()""", {"a": 3})
3

只需传递一个 globals 字典。

简单的解释就是你在本地命名空间中传入funcaFunction 无权访问您传入的本地人(它有自己的本地人)。所有 aFunction 都可以访问它自己的局部变量和它的模块的全局变量。 func 不在其中,因此函数调用失败。

大多数普通模块使用它们的全局变量和局部变量作为相同的命名空间(您可以自己检查一下 assert globals() is locals())。这就是为什么您可以在模块级别定义事物并让它们可用于任何已定义的函数(模块中的所有名称都是自动全局的)。

要完成这项工作,您需要使局部变量和全局变量相同 dict,或者根本不传递局部变量。如果您不希望全局变量发生变异,则只需复制全局变量,然后向其中添加 func

src = """
def aFunction():
    a = func.func_1()
    b = func.func_2()
    return a + b
value = aFunction()
"""

class Functions:
    def func_1(self):
        return "first"
    def func_2(self):
        return "second"
functions = Functions()

compiled_code = compile(src, "<string>", "exec")

global_env = {}
local_env = {"func":functions}
namespace = dict(global_env)
namespace.update(local_env)
exec(compiled_code, namespace)
print(namespace["value"])