在不同模块中使用类

7 投票
7 回答
818 浏览
提问于 2025-04-17 02:44

我想修改标准库中的一些类,让它们使用一组不同的全局变量,而不是该模块中其他类使用的那些全局变量。

示例

这个示例只是用来说明问题:

# module_a.py

my_global = []

class A:
    def __init__(self):
        my_global.append(self)

class B:
    def __init__(self):
        my_global.append(self)

在这个示例中,如果我通过 A() 创建一个 A 的实例,它会调用名为 my_global 的对象上的 append 方法。但现在我想创建一个新模块,把 B 导入到这个模块中,并让 B 使用它被导入模块中的 my_global,而不是 B 原本定义的模块中的 my_global

# module_b.py

from module_a import B

my_global = []

相关内容

我在努力解释我的问题,这里是我之前的尝试,实际上问的是完全不同的内容:

更新0

  • 上面的示例只是为了说明我想要实现的目标。
  • 由于类没有变量作用域(不像C++那样),我认为全局变量的引用并不是存储在类中的,而是附加在每个函数定义时。

更新1

有人要求提供一个来自标准库的示例:

threading 模块中,许多(也许是所有?)类都使用了像 _allocate_lockget_ident_active 这样的全局变量,这些变量在 这里这里 定义。如果不改变这些全局变量,就无法只改变该模块中某个类的行为。

7 个回答

1

好的,这里有一个概念验证,展示了如何做到这一点。需要注意的是,它只处理一层深的情况——属性和嵌套函数没有被调整。要实现这一点,并让这个方法更稳健,每个函数的globals()应该和需要替换的globals()进行比较,只有在它们相同的情况下才进行替换。

def migrate_class(cls, globals):
    """Recreates a class substituting the passed-in globals for the
    globals already in the existing class.  This proof-of-concept
    version only goes one-level deep (i.e. properties and other nested
    functions are not changed)."""
    name = cls.__name__
    bases = cls.__bases__
    new_dict = dict()
    if hasattr(cls, '__slots__'):
        new_dict['__slots__'] = cls.__slots__
        for name in cls.__slots__:
            if hasattr(cls, name):
                attr = getattr(cls, name)
                if callable(attr):
                    closure = attr.__closure__
                    defaults = attr.__defaults__
                    func_code = attr.__code__
                    attr = FunctionType(func_code, globals)
                new_dict[name] = attr
    if hasattr(cls, '__dict__'):
        od = getattr(cls, '__dict__')
        for name, attr in od.items():
            if callable(attr):
                closure = attr.__closure__
                defaults = attr.__defaults__
                kwdefaults = attr.__kwdefaults__
                func_code = attr.__code__
                attr = FunctionType(func_code, globals, name, defaults, closure)
                if kwdefaults:
                    attr.__kwdefaults__ = kwdefaults
            new_dict[name] = attr
    return type(name, bases, new_dict)

在完成这个练习后,我真的很好奇你为什么需要这样做?

1

“你不能改变这些全局变量,否则所有在这个模块里的类都会受到影响。”这就是问题的根源,对吧?这也很好地解释了全局变量的一些问题。在线程中使用全局变量会让这些类和全局对象紧紧绑在一起。

当你费尽心思去找出并修改每个类中使用全局变量的地方时,难道不觉得不如直接重新写一份代码来得简单吗?

在你的情况下,唯一可能有用的解决办法是类似于mock的东西。Mock的补丁装饰器或上下文管理器(或者类似的东西)可以在某个对象的生命周期内替换掉一个全局变量。这在单元测试的严格环境中效果很好,但在其他情况下我不推荐这样做,还是考虑直接重新实现代码来满足自己的需求吧。

5

你不能改变全局变量,因为那样会影响到使用这个模块的其他用户。不过,你可以做的是创建这个模块的一个私有副本。

我相信你对 sys.modules 这个概念不陌生。如果你把一个模块从这里移除,Python 就会忘记这个模块被导入过,但之前引用它的对象仍然会继续使用它。再次导入时,会创建这个模块的新副本。

解决你问题的一个“变通”方法可能是这样的:

import sys
import threading

# Remove the original module, but keep it around
main_threading = sys.modules.pop('threading')

# Get a private copy of the module
import threading as private_threading

# Cover up evidence by restoring the original
sys.modules['threading'] = main_threading

# Modify the private copy
private_threading._allocate_lock = my_allocate_lock()

现在,private_threading.Lock 的全局变量和 threading.Lock 完全分开了!

当然,这个模块并不是为了这种情况而设计的,尤其是像 threading 这样的系统模块,你可能会遇到一些问题。例如,threading._active 应该包含所有正在运行的线程,但用这个方法的话,_active 可能就不会包含所有线程了。这个代码可能还会让你的袜子消失,甚至把你的房子烧了等等。一定要认真测试。

撰写回答