从另一个模块导入的函数中覆盖全局变量

4
假设我有两个模块:
a.py
value = 3
def x()
    return value

b.py

from a import x
value = 4

我的目标是在b中使用a.x的功能,但更改函数返回的值。具体来说,在运行b.x()时,将使用a作为全局名称源查找value。我基本上正在尝试创建一个与a.x完全相同的函数对象副本,但它使用b来获取其全局变量。有没有一个相当简单的方法来做到这一点?
下面是一个示例:
import a, b

print(a.x(), b.x())

目前的结果是3 3,但我希望它是3 4
我想到了两种复杂的方法可以解决这个问题,但我对它们都不满意:
  1. Re-define x in module b using copy-and paste. The real function is much more complex than shown, so this doesn't sit right with me.
  2. Define a parameter that can be passed in to x and just use the module's value:

    def x(value):
        return value
    

    This adds a burden on the user that I want to avoid, and does not really solve the problem.

有没有一种方法可以修改函数获取其全局变量的位置?

@liliscent 不确定那是什么,但我添加了一个确切的示例,说明我想要什么。 - Mad Physicist
如果你只想修改另一个模块中的变量,只需使用a.value来访问它。我不知道你为什么提到函数x() - llllllllll
@liliscent。现在可能更清楚了吗? - Mad Physicist
我认为你需要一个类而不是全局变量。本质上,你想要两个相同的函数在不同的“值”上操作。这个“值”应该是一个类成员。 - llllllllll
@liliscent。我可以为一个类做到这一点。但是,我有一个情况,无法避免使用全局变量,如所示,但我想替换它们的来源。如果能解释为什么不可能,那也会回答我的问题。然而,我怀疑,由于这是Python,肯定有方法可以做到。 - Mad Physicist
显示剩余2条评论
3个回答

5
我通过猜测和研究的混合方式提出了一种解决方案。你可以几乎完全按照我在问题中提出的建议进行操作:复制函数对象并替换它的__globals__属性。
我正在使用Python 3,因此这里是链接上述问题的答案的修改版本,增加了一个覆盖全局变量的选项:
import copy
import types
import functools

def copy_func(f, globals=None, module=None):
    """Based on https://dev59.com/EWYr5IYBdhLWcg3wpb2Q#13503277 (@unutbu)"""
    if globals is None:
        globals = f.__globals__
    g = types.FunctionType(f.__code__, globals, name=f.__name__,
                           argdefs=f.__defaults__, closure=f.__closure__)
    g = functools.update_wrapper(g, f)
    if module is not None:
        g.__module__ = module
    g.__kwdefaults__ = copy.copy(f.__kwdefaults__)
    return g

b.py

from a import x
value = 4
x = copy_func(x, globals(), __name__)
__globals__ 属性是只读的,因此必须将其传递给 FunctionType 的构造函数。现有函数对象的 __globals__ 引用不能更改。 附言 我已经使用了很多次,它被实现在我编写和维护的实用库中,名为 haggis。请参见 haggis.objects.copy_func

@chrisz. 我已经添加了一个选项,甚至可以伪造函数所定义的模块。 - Mad Physicist

1

所以我找到了一种方法(有点)可以做到这一点,尽管我不认为它完全解决了你的问题。使用inspect,您可以访问调用函数的文件的全局变量。因此,如果您设置文件如下:

a.py

import inspect

value = 3

def a():
    return inspect.stack()[1][0].f_globals['value']

b.py

from a import a

value = 5

print(a())

输出结果为5,而不是3。但是,如果将它们都导入到第三个文件中,它将查找第三个文件的全局变量。只是想分享这个片段。

我发现了更加简单的方法,但是还是要给你点赞+1。 - Mad Physicist
你能分享一下你的想法吗?我之前也遇到过类似的问题,很想了解你的方法。 - user3483203
我已经添加了一个答案。它比你的答案稍微长一些,但是对于我想要的目的来说更加正确。也许它对你也有用。 - Mad Physicist

1
我遇到了同样的问题。但是我记得有 eval 这个函数。
如果你不需要参数,这里有一个更简短的版本:
b.py:
from a import x as xx

# Define globals for the function here
glob = {'value': 4}
def x():
    return eval(xx.__code__, glob)


希望两年后它仍然有所帮助。

这假设的接口比我想象中更具体,但在某些情况下肯定有效。此外,这是比通常更好地使用 eval - Mad Physicist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接