我想修改标准库中的一些类,以使用与该模块中其他类使用的不同的全局变量集。
此示例仅为示例:
# 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()
的实例,则会在append
指定的对象上调用my_global
。但是现在我想创建一个新模块,向其导入B
,并让B
使用my_global
来自导入的模块,而不是my_global
来自模块B
是原始定义的。
# module_b.py
from module_a import B
my_global = []
我正在努力解释我的问题,这是我之前的尝试,实际上是在做一些完全不同的事情:
标准库要求提供一个示例:
threading
模块中的许多(可能全部?)类都使用了_allocate_lock
,get_ident
和_active
等全局变量,定义了{{3} }和here。如果没有为该模块中的所有类更改它们,就无法更改这些全局变量。
答案 0 :(得分:5)
你不能在不影响模块的所有其他用户的情况下更改全局变量,但是 可以做的是创建整个模块的私有副本。
我相信你熟悉sys.modules
,如果你从那里删除一个模块,Python会忘记导入它,但引用它的旧对象将继续这样做。再次导入时,将创建该模块的新副本。
对你的问题的一个hacky解决方案可能是这样的:
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
都不会拥有它们。该代码也可能会吃掉你的袜子,让你的房子着火等等。严格测试。
答案 1 :(得分: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)
在完成这项练习后,我真的好奇你为什么需要这样做?
答案 2 :(得分:1)
“如果没有为该模块中的所有类更改这些全局变量,就无法更改这些全局变量。”问题的根源不在于它,并且通常对global
变量的问题有一个很好的解释。在线程中使用globals
将其类绑定到那些全局对象。
当你在模块的单个类中使用一个全局变量时,你需要一些东西来查找和修补它们,你是否还需要重新实现代码以供自己使用?
在你的情况下,“可能”使用的唯一工作就像mock。 Mock的补丁装饰器/上下文管理器(或类似的东西)可以用来替换给定对象生命周期的全局变量。它在单元测试的受控环境中运行良好,但在任何其他情况下我都不会推荐它,并且会考虑重新实现代码以满足我的需求。
答案 3 :(得分:0)
Globals正是因为这个原因而不好,因为我相信你已经知道了。
我尝试在我自己的模块中重新实现A和B(可能通过子类化)并且所有引用都是 my_global被A和B上的注入依赖所取代,我在这里称之为注册表。
class A(orig.A):
def __init__(self, registry):
self.registry = registry
self.registry.append(self)
# more updated methods
如果您要自己创建A的所有实例,那么您已经完成了很多工作。您可能想要创建一个隐藏新init参数的工厂。
my_registry = []
def A_in_my_registry():
return A(my_registry)
如果外国代码为您创建了orig.A实例,而您希望拥有新的A实例,则必须希望外部代码可以自定义 与工厂。如果没有,则从外来类派生并更新它们以使用(新注入)工厂代替。 ....然后冲洗重复以创建更新的课程。我意识到这可能是乏味的,几乎不可能,这取决于外部代码的复杂性,但大多数标准库都非常扁平。
-
编辑:Monkey patch std lib代码。
如果你不介意猴子修补std libs,你也可以尝试修改原始类来工作 具有默认为原始全局变量的重定向级别,但每个实例可自定义:
import orig
class A(orig.A):
def __init__(self, registry=orig.my_globals):
self.registry = registry
self.registry.append(self)
# more updated methods
orig.A = A
和以前一样,你需要控制应该使用非“标准全局”的A的创作, 但只要你足够早地修补补丁,就不会有不同的A类。
答案 4 :(得分:0)
如果你使用Python 3,你可以继承B并重新定义__globals__
方法的__init__
属性,如下所示:
from module_a import B
function = type(lambda: 0) # similar to 'from types import FunctionType as function', but faster
my_global = []
class My_B (B):
__init__ = function(B.__init__.__code__, globals(), '__init__', B.__init__.__defaults__, B.__init__.__closure__)
答案 5 :(得分:-2)
恕我直言,无法覆盖全局变量...
答案 6 :(得分:-4)
Globals很少是一个好主意。
隐含变量很少是一个好主意。
一个隐含使用的全球很容易被起诉,也很“好”。
此外,你不希望A.__init__()
做任何“类级”的事情,比如更新整个类中存在的一些神秘集合。这通常是一个坏主意。
您希望module_a
中的工厂(1)创建A
或B
个实例并且(b),而不是混淆隐含的类级别集合)更新显式集合。
然后,您可以在module_b
中使用此工厂,但不同的收藏品除外。
这可以通过暴露隐式依赖来提升可测试性。
module_a.py
class Factory( object ):
def __init__( self, collection ):
self.collection= collection
def make( self, name, *args, **kw ):
obj= eval( name )( *args, **kw )
self.collection.append( obj )
return obj
module_collection = []
factory= Factory( module_collection )
module_b.py
module_collection = []
factory = module_a.Factory( module_collection )
现在客户可以这样做
import module_b
a = module_b.factory.make( "A" )
b = module_b.factory.make( "B" )
print( module_b.module_collection )
通过使工厂“可调用”(实现__call__
而不是make
),您可以使API更流畅。
重点是通过工厂类使显式集合。