7

我想修改标准库中的一些类以使用该模块中其他类使用的一组不同的全局变量。

例子

这个例子只是一个例子:

# module_a.py

my_global = []

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

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

在这个例子中,如果我创建一个Avia的实例A(),它将调用append名为 by 的对象my_global。但是现在我希望创建一个新模块,导入B它,并从它被导入的模块中B使用,而不是原来定义的模块。my_globalmy_globalB

# module_b.py

from module_a import B

my_global = []

有关的

我正在努力解释我的问题,这是我之前的尝试,实际上确实提出了一些完全不同的问题:

更新0

  • 上面的示例仅用于说明我要实现的目标。
  • 由于类没有变量范围(与 C++ 不同),我认为对全局映射的引用不存储在类中,而是在定义时附加到每个函数。

更新1

向标准库请求了一个示例:

模块中的许多(也许是全部?)类threading都使用了全局变量,例如_allocate_lock,get_ident_active, 定义的 herehere。如果不对该模块中的所有类进行更改,就无法更改这些全局变量。

4

7 回答 7

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不会拥有它们。代码还可能会吃掉你的袜子,让你的房子着火等等。严格测试。

于 2011-10-09T18:42:03.647 回答
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)

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

于 2011-10-06T23:09:13.150 回答
1

“如果不更改该模块中的所有类,就无法更改这些全局变量。” 这不是问题的根源,而且是对global一般变量问题的一个很好的解释。globals线程中的使用将其类与那些全局对象联系在一起。

当您在模块的单个类中对全局变量的每次使用进行 jerry-rig 查找和猴子修补时,您是否比重新实现代码供自己使用更进一步?

在您的情况下“可能”使用的唯一解决方法是mock。Mock 的补丁装饰器/上下文管理器(或类似的东西)可用于在给定对象的生命周期内交换全局变量。它在单元测试的非常受控的上下文中运行良好,但在任何其他情况下我都不会推荐它,并且会考虑重新实现代码以满足我的需要。

于 2011-10-07T02:20:22.037 回答
0

正是因为这个原因,全局变量不好,我相信你已经足够了解了。

我会尝试在我自己的模块中重新实现 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 实例,则您必须希望外部代码可以通过工厂自定义。如果不是,则从外部类派生并更新它们以使用(新注入的)A 工厂。.... 并为创建这些更新的类而重复冲洗。我意识到根据外部代码的复杂性,这可能很乏味甚至几乎是不可能的,但是大多数标准库都非常平坦。

--

编辑:猴子补丁标准库代码。

如果您不介意猴子修补标准库,您还可以尝试修改原始类以使用默认为原始全局变量的重定向级别,但每个实例可自定义:

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 类。

于 2011-10-07T22:13:11.243 回答
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__)
于 2011-10-11T12:24:17.143 回答
-2

恕我直言,无法覆盖全局变量...

于 2011-09-21T07:28:41.257 回答
-4

全局变量很少是一个好主意。

隐式变量很少是一个好主意。

隐式使用的全局变量很容易被指控为“很少好”。

此外,您不想A.__init__()做任何“类级别”的事情,比如更新整个类中存在的一些神秘集合。这通常是个坏主意。

与其弄乱隐式类级别的集合,不如在(1)创建或实例和(b)更新显式集合中使用工厂。module_aAB

然后,您可以在 中使用此工厂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.

关键是通过工厂类使集合显式。

于 2011-09-21T09:54:52.143 回答