在不同模块的上下文中使用类

时间:2011-09-21 07:10:49

标签: python module namespaces python-3.x globals

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

实施例

此示例仅为示例:

# 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 = []

相关

我正在努力解释我的问题,这是我之前的尝试,实际上是在做一些完全不同的事情:

Update0

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

UPDATE1

标准库要求提供一个示例:

threading模块中的许多(可能全部?)类都使用了_allocate_lockget_ident_active等全局变量,定义了{{3} }和here。如果没有为该模块中的所有类更改它们,就无法更改这些全局变量。

7 个答案:

答案 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)创建AB个实例并且(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更流畅。

重点是通过工厂类使显式集合。