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

发布于 2024-12-05 19:24:53 字数 1413 浏览 1 评论 0 原文

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

示例

此示例仅是一个示例:

# 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 的实例,它将调用 appendmy_global 命名的对象上。但现在我希望创建一个新模块,将 B 导入到其中,并让 B 使用导入它的模块中的 my_global ,而不是模块 B 中的 my_global 是最初定义的。

# module_b.py

from module_a import B

my_global = []

相关

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

Update0

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

Update1

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

threading 模块使用诸如 _allocate_lockget_ident 等全局变量,以及_active,定义于此处此处。如果不更改该模块中所有类的全局变量,就无法更改这些全局变量。

I want to modify some classes in the standard library to use a different set of globals the ones that other classes in that module use.

Example

This example is an example only:

# module_a.py

my_global = []

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

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

In this example, If I create an instance of A, via A(), it will call append on the object named by my_global. But now I wish to create a new module, import B to it, and have B use my_global from the module it's been imported into, instead of the my_global from the module B was original defined.

# module_b.py

from module_a import B

my_global = []

Related

I'm struggling to explain my problem, here is my previous attempt which did in fact ask something completely different:

Update0

  • The example above is only for illustration of what I'm trying to achieve.
  • Since there is no variable scope for classes (unlike say, C++), I think a reference to a globals mapping is not stored in a class, but instead is attached to every function when defined.

Update1

An example was requested from the standard library:

Many (maybe all?) of the classes in the threading module make use of globals such as _allocate_lock, get_ident, and _active, defined here and here. One cannot change these globals without changing it for all the classes in that module.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

冰魂雪魄 2024-12-12 19:24:53

您无法在不影响模块的所有其他用户的情况下更改全局变量,但您可以做的是创建整个模块的私有副本。

我相信您熟悉 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._active 应该包含所有正在运行的线程,但使用此解决方案,_active 都不会包含所有线程。该代码还可能会吃掉你的袜子、放火烧你的房子等等。请严格测试。

You can't change the globals without affecting all other users of the module, but what you sort of can do is create a private copy of the whole module.

I trust you are familiar with sys.modules, and that if you remove a module from there, Python forgets it was imported, but old objects referencing it will continue to do so. When imported again, a new copy of the module will be made.

A hacky solution to your problem could would be something like this:

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()

And now, private_threading.Lock has globals entirely separate from threading.Lock!

Needless to say, the module wasn't written with this in mind, and especially with a system module such as threading you might run into problems. For example, threading._active is supposed to contain all running threads, but with this solution, neither _active will have them all. The code may also eat your socks and set your house on fire, etc. Test rigorously.

冷月断魂刀 2024-12-12 19:24:53

好的,这是一个展示如何做到这一点的概念验证。请注意,它只深入一层——属性和嵌套函数不会调整。为了实现这一点,并使其更加健壮,每个函数的 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)

经过这次练习后,我真的很好奇为什么需要这样做?

Okay, here's a proof-of-concept that shows how to do it. Note that it only goes one level deep -- properties and nested functions are not adjusted. To implement that, as well as make this more robust, each function's globals() should be compared to the globals() that should be replaced, and only make the substitution if they are the same.

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)

After having gone through this excercise, I am really curious as to why you need to do this?

入怼 2024-12-12 19:24:53

“如果不更改该模块中所有类的全局变量,就无法更改这些全局变量。”这就是问题的根源不是吗,并且总体上很好地解释了全局变量的问题。在线程中使用globals将其类与那些全局对象联系起来。

当您临时搭建一些东西来查找并猴子修补模块中单个类中全局变量的每次使用时,您是否比重新实现代码以供自己使用更进一步?

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

"One cannot change these globals without changing it for all the classes in that module." That's the root of the problem isn't it, and a good explanation of the problem with global variables in general. The use of globals in threading tethers its classes to those global objects.

By the time you jerry-rig something to find and monkey patch each use of a global variable within an individual class from the module, are you any further ahead of just reimplementing the code for your own use?

The only work around that "might" be of use in your situation is something like mock. Mock's patch decorators/context managers (or something similar) could be used to swap out a global variable for the life-time of a given object. It works well within the very controlled context of unit testing, but in any other circumstances I wouldn't recommend it and would think about just reimplementing the code to suit my needs.

被你宠の有点坏 2024-12-12 19:24:53

正是由于这个原因,全局变量是不好的,我相信你很清楚。

我会尝试在我自己的模块中重新实现 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 的所有实例,那么您就已经完成了。您可能想要创建一个工厂来隐藏新的初始化参数。

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

Globals are bad for exactly this reason, as I am sure you know well enough.

I'd try to reimplement A and B (maybe by subclassing them) in my own module and with all references to
my_global replaced by an injected dependency on A and B, which I'll call registry here.

class A(orig.A):

    def __init__(self, registry):
        self.registry = registry
        self.registry.append(self)

    # more updated methods

If you are creating all instances of A yourself you are pretty much done. You might want to create a factory which hides away the new init parameter.

my_registry = []
def A_in_my_registry():
    return A(my_registry)

If foreign code creates orig.A instances for you, and you would rather have new A instances, you have to hope the foreign code is customizeable
with factories. If not, derive from the foreign classes and update them to use (newly injected) A factories instead. .... And rinse repeat for for the creation of those updated classes. I realize this can be tedious to almost impossible depending on the complexity of the foreign code, but most std libs are quite flat.

--

Edit: Monkey patch std lib code.

If you don't mind monkey patching std libs, you could also try to modifiy the original classes to work
with a redirection level which defaults to the original globals, but is customizable per instance:

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

As before you will need to control creations of A which should use non "standard globals",
but you won't have different A classes around as long as you monkey patch early enough.

迷离° 2024-12-12 19:24:53

如果您使用Python 3,您可以子类化B并重新定义__init__方法的__globals__属性,如下所示:

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__)

If you use Python 3, you can subclass B and redefine the __globals__ attribute of the __init__ method like this:

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__)
洒一地阳光 2024-12-12 19:24:53

恕我直言,不可能覆盖全局变量......

IMHO it is not possible to override global variables...

巴黎夜雨 2024-12-12 19:24:53

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

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

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

此外,您不希望 A.__init__() 执行任何“类级别”的操作,例如更新整个类中存在的一些神秘集合。这通常是个坏主意。

您希望在 module_a 中有一个 Factory 来 (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 )

)来使 API 更加流畅。

您可以通过使工厂“可调用”(实现 __call__ 而不是 make 重点是通过工厂类使集合显式

Globals are rarely a good idea.

Implicit variables are rarely a good idea.

An implicitly-used global is easy to indict as also "rarely good".

Additionally, you don't want A.__init__() doing anything "class-level" like updating some mysterious collection that exists for the class as a whole. That's often a bad idea.

Rather than mess with implicit class-level collection, you want a Factory in module_a that (1) creates A or B instances and (b) updates an explicit collection.

You can then use this factory in module_b, except with a different collection.

This can promote testability by exposing an implicit dependency.

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 )

Now a client can do this

import module_b
a = module_b.factory.make( "A" )
b = module_b.factory.make( "B" )
print( module_b.module_collection )

You can make the API a bit more fluent by making the factory "callable" (implementing __call__ instead of make.

The point is to make the collection explicit via a factory class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文