在不同模块中使用类
我想修改标准库中的一些类,让它们使用一组不同的全局变量,而不是该模块中其他类使用的那些全局变量。
示例
这个示例只是用来说明问题:
# 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_lock
、get_ident
和 _active
这样的全局变量,这些变量在 这里 和 这里 定义。如果不改变这些全局变量,就无法只改变该模块中某个类的行为。
7 个回答
好的,这里有一个概念验证,展示了如何做到这一点。需要注意的是,它只处理一层深的情况——属性和嵌套函数没有被调整。要实现这一点,并让这个方法更稳健,每个函数的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)
在完成这个练习后,我真的很好奇你为什么需要这样做?
“你不能改变这些全局变量,否则所有在这个模块里的类都会受到影响。”这就是问题的根源,对吧?这也很好地解释了全局变量的一些问题。在线程中使用全局变量会让这些类和全局对象紧紧绑在一起。
当你费尽心思去找出并修改每个类中使用全局变量的地方时,难道不觉得不如直接重新写一份代码来得简单吗?
在你的情况下,唯一可能有用的解决办法是类似于mock的东西。Mock的补丁装饰器或上下文管理器(或者类似的东西)可以在某个对象的生命周期内替换掉一个全局变量。这在单元测试的严格环境中效果很好,但在其他情况下我不推荐这样做,还是考虑直接重新实现代码来满足自己的需求吧。
你不能改变全局变量,因为那样会影响到使用这个模块的其他用户。不过,你可以做的是创建这个模块的一个私有副本。
我相信你对 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
可能就不会包含所有线程了。这个代码可能还会让你的袜子消失,甚至把你的房子烧了等等。一定要认真测试。