更改一个模块中的模块变量,而原始模块中的变量不变

发布于 2024-12-27 23:38:29 字数 501 浏览 0 评论 0原文

我正在使用 foo 模块,其中包含一些指示其行为的模块级变量。这些变量之一是 SANITIZE。

我希望能够以两种方式使用 foo,foo.parse() 和 foo2.parse(),其中唯一的区别是 foo 有 SANITIZE=false 而 foo2 有 SANITIZE=true

我想在不必复制的情况下执行此操作粘贴我的代码。例如

#foo module
SANITIZE='foo'
def parse():
    print SANITIZE

#foo2 module
import foo
foo.SANITIZE='foo2'


#test script
import foo
import foo2
foo2.foo.parse() #i want this to print 'foo2'
foo.parse() #i want this to print 'foo'

但是,上面的示例将两次打印“foo2”。有没有办法实现这种行为?

谢谢!

I'm using the foo module which includes a few module level variables that dictate its behavior. One of these variables is SANITIZE.

I want to be able to use foo in two ways, foo.parse() and foo2.parse(), where the only difference is foo has SANITIZE=false and foo2 has SANITIZE=true

I want to do this while not having to copy paste my code. For example

#foo module
SANITIZE='foo'
def parse():
    print SANITIZE

#foo2 module
import foo
foo.SANITIZE='foo2'


#test script
import foo
import foo2
foo2.foo.parse() #i want this to print 'foo2'
foo.parse() #i want this to print 'foo'

However, the above example will print 'foo2' both times. Is there a way to achieve this behavior?

Thanks!

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

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

发布评论

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

评论(2

|煩躁 2025-01-03 23:38:29

如果这是您的代码,那么解决方案不是依赖于模块级变量,而是以其他方式来保持对象的状态。 Python 中的模块是“单例”——这意味着一旦被任何模块导入,在解释器范围内就只有一个版本——
例如,如果您使用类继承,您想要的行为正是您免费获得的 - 子类可以自定义一些父类,但无需重写全部父类。

因此,如果您将“foo”代码封装在一个类中 - 您甚至可能不需要编写一个需要实例的类,您就已经获得了您想要的功能:

#foo module:

class Foo(object):
    sanitize = "value 1"
    @classmethod
    def parse(cls):
        print cls.sanitize

#foo2
from foo import Foo
class Foo2(Foo):
    sanitize = "value 2"

# code:
from foo import Foo
from foo2 import Foo2

Foo.parse()
Foo2.parse()

当然,通过大量的内省和元编程, Python 允许,它可以做你想要的事情 - 但这会使事情变得复杂,而且没有好处。模块变量与 C 代码中的全局变量具有大部分相同的缺点。

一种方法是在通过 foo2 访问时创建“foo”,以动态更改变量,调用“foo”中的函数并在退出时恢复之前的值。
对于要在模块“foo2”上的“foo”的属性访问上触发的任意代码,“foo”必须引用具有“property”属性的对象 -

因此,您编写的 exa 示例将同时运行-不安全的方式,顺便说一句,非常不安全的方式,如果 foo2 或多或少这样写:

import foo as _foo

SANITIZE = "value2"

class _FooWrapper(object):
    def __getattribute__(self, attr):
        self.change_vars()
        original_function = getattr(_foo, attr)
        if callable(original):
            def wrapper(func):
                def new_func(*args, **kw):
                    res = func(*args, **kw)
                    self.restore_vars()
                    return res
                return new_func
            return wrapper(original)
        return original

    def change_vars(self):
        self.original_sanitize = _foo.SANITIZE
        _foo.SANITIZE = SANITIZE
    def restore_vars(self):
        __foo.SANITIZE = self.original_sanitize

foo = _FooWrapper()

这会在模块 foo2 中创建一个“foo”对象,当访问该对象时,会从原始“foo”模块中检索任何请求的属性。因此,“foo2.foo.parse”将获得“foo”解析函数 - 但是,感知这种方法中的“hackiness”量 - 以便能够恢复位于“foo”模块内部的原始值Python 解释器,从 foo2 获取函数后,该函数在返回时也会恢复值本身。这样做的唯一方法是修改函数,以便它在返回时运行附加代码 - 因此它是由上面的代码动态修饰的。

我认为这个例子清楚地表明,在这种情况下,模块级配置并不是可行的方法,尽管这是可能的。

编辑
OP 评论道:

谢谢jsbueno,不幸的是这不是我的代码,我必须依赖
模块级别变量。 Wrapper类方法很有趣
但正如你所说,非常hacky并且令人难以置信的非线程安全,我是
恐怕不适合我的情况

回复:

模块是“单例” - 因此,更改模块上的变量,位于
任何一点都会使其线程不安全。我可以考虑的另一种方式
这是创建一个实际上重新创建的“复印机”模块
另一个现有模块的类、属性和实例,当
导入 - 重新绑定所有函数全局变量(方法将
仍可作为此级别的函数进行访问)

阅读此描述可能听起来“不可行” - 但它比描述的更容易 -
下面是我的“foo2”模块,它执行上述操作:

from types import ModuleType, FunctionType

import foo as _foo

SANITIZE = "value 2"

def rebuild_function(func, glob):
    """Rebinds the globals in the given functions to globals in 
    this module  by default"""
    new_func = FunctionType(func.func_code,
                            glob,
                            func.func_name,
                            func.func_defaults,
                            func.func_closure)
    return new_func

def rebuild_class(cls, glob):
    metatype = type(cls)
    dct = cls.__dict__.copy()
    for key, value in dct.items():
        if isinstance(value, FunctionType):
            dct[key] = rebuild_function(value, glob)
    return metatype(cls.__name__, cls.__bases__, dct)

def rebuild_module(mod,glob):
    new_module = ModuleType(mod.__name__)
    for key, value in mod.__dict__.items():
        if isinstance(value, FunctionType):
            value = rebuild_function(value, glob)
        elif isinstance(value, type):
            value = rebuild_class(value, glob)
        setattr(new_module, key, value)
    return new_module

foo = rebuild_module(_foo, globals())

__all__ = ["foo", "SANITIZE"]

此代码完全按照我的描述进行操作 - 它重新创建原始模块中的所有函数对象,重新绑定每个函数的全局字典。它是并发安全的。如果要克隆的模块确实指向本机代码类或函数(它们不是“FunctionType”),则存在一些极端情况。如果它大量使用多个类继承、元类,那么它可能有效,也可能无效。

我用一个简单的类对其进行了测试,效果很好:

#module "foo"
SANITIZE='foo'
def parse():
    print SANITIZE

class Parser(object):
    def __init__(self):
        print SANITIZE * 2

输出

#test script
import foo
import foo2
foo2.foo.parse() #i want this to print 'foo2'
foo2.foo.Parser()
foo.parse() #i want this to print 'foo'
foo.Parser()

[gwidion@powerpuff tmp16]$ python test_foo.py 
foofoo
value 2
value 2value 2
foo
foofoo
[gwidion@powerpuff tmp16]$ 

If this is your code, than the solution is not to depend in module level variables, but in some other way to keep state of the objects. Modules in Python are "singletons" - which means that once imported by any module, there is just one version of then, interpreter wide -
The behavior you want, for example, is exactly what you get, for free, if you use class inheritance - the child class can customize some, but no need to rewrite all, of the parent class.

So, if you as much as encapsulate your "foo" code inside a class - you may not even have to write a class that needs instances, you get the features you want already:

#foo module:

class Foo(object):
    sanitize = "value 1"
    @classmethod
    def parse(cls):
        print cls.sanitize

#foo2
from foo import Foo
class Foo2(Foo):
    sanitize = "value 2"

# code:
from foo import Foo
from foo2 import Foo2

Foo.parse()
Foo2.parse()

Of course, with the cheer amount of introspection and metaprogramming that Python allows, it would be possible to do something like what you want - but it would complicate matters, for no good. Module variables have most of the same disadvantages that global variables have in C code.

One way to do it is to make "foo" when accessed through foo2 to make the variable changes on the fly, call the function in "foo" and restore the previosu values on exit.
For arbitrary code to be triggered on attribute access of "foo" on the module "foo2", "foo" has to refer to an object with a "property" attribute --

So, the exa example you wrote would run, in a concurency-unsafe way, btw, very unsafe way, if foo2 is written more or less so:

import foo as _foo

SANITIZE = "value2"

class _FooWrapper(object):
    def __getattribute__(self, attr):
        self.change_vars()
        original_function = getattr(_foo, attr)
        if callable(original):
            def wrapper(func):
                def new_func(*args, **kw):
                    res = func(*args, **kw)
                    self.restore_vars()
                    return res
                return new_func
            return wrapper(original)
        return original

    def change_vars(self):
        self.original_sanitize = _foo.SANITIZE
        _foo.SANITIZE = SANITIZE
    def restore_vars(self):
        __foo.SANITIZE = self.original_sanitize

foo = _FooWrapper()

This creates a "foo" object in module foo2 that when accessed, retrieves any requested attributes from the original "foo" module instead. So "foo2.foo.parse" will get the "foo" parse function - but, perceive the ammount of "hackiness" in this approach - in order to be able to restore the original value in the "foo" module that lives inside the Python interpreter, after the function was fetched from foo2, that function have too, upon returning, restore the values itself. The only way to do so is modifying the function so that it runs additional code when it returns - so it is decorated on the fly by the code above.

I think this example makes clear that having module level configurations is not the way to go in this case, although possible.

EDIT
The O.P. commented:

Thanks jsbueno, unfortunately this is not my code and I must rely on
the module level variables. The Wrapper class method is interesting
but as you said, very hacky and incredibly non thread safe, which I'm
afraid won't do for my case

In reply to that:

Modules are "singletons" - so, changing the variable on the module, at
any point will make it thread unsafe. The other way I can think about
this is creating a "photocopier" module that actually re-creates
classes, attributes and instances of another, existing module, when
imported - rebinding all functions global variables (methods would
still be acessed as functions at this level)

Reading this description it may sound as "unfeasable" - but it is easier done than described -
Here follows my "foo2" module that does the above:

from types import ModuleType, FunctionType

import foo as _foo

SANITIZE = "value 2"

def rebuild_function(func, glob):
    """Rebinds the globals in the given functions to globals in 
    this module  by default"""
    new_func = FunctionType(func.func_code,
                            glob,
                            func.func_name,
                            func.func_defaults,
                            func.func_closure)
    return new_func

def rebuild_class(cls, glob):
    metatype = type(cls)
    dct = cls.__dict__.copy()
    for key, value in dct.items():
        if isinstance(value, FunctionType):
            dct[key] = rebuild_function(value, glob)
    return metatype(cls.__name__, cls.__bases__, dct)

def rebuild_module(mod,glob):
    new_module = ModuleType(mod.__name__)
    for key, value in mod.__dict__.items():
        if isinstance(value, FunctionType):
            value = rebuild_function(value, glob)
        elif isinstance(value, type):
            value = rebuild_class(value, glob)
        setattr(new_module, key, value)
    return new_module

foo = rebuild_module(_foo, globals())

__all__ = ["foo", "SANITIZE"]

This code does exactly what I described - it recreates all function objects in the original module, rebindign the globals dict for each function. It is concurrency safe. There are some corner cases if the to-be clonned module does point to native code classes or functions (they are not of "FunctionType"). If it makes heavy usage of multiple class inheritance, metaclasses,- it miught work, or not.

I tested it with a simple class and it worked fine:

#module "foo"
SANITIZE='foo'
def parse():
    print SANITIZE

class Parser(object):
    def __init__(self):
        print SANITIZE * 2

And

#test script
import foo
import foo2
foo2.foo.parse() #i want this to print 'foo2'
foo2.foo.Parser()
foo.parse() #i want this to print 'foo'
foo.Parser()

Output:

[gwidion@powerpuff tmp16]$ python test_foo.py 
foofoo
value 2
value 2value 2
foo
foofoo
[gwidion@powerpuff tmp16]$ 
孤单情人 2025-01-03 23:38:29

您的问题看起来与此处描述的非常相似: http ://effbot.org/pyfaq/how-do-i-share-global-variables-across-modules.htm

File: config.py (your foo.py)
x = 0   # Default value of the 'x' configuration setting

File: mod.py (your foo2.py)
import config
config.x = 1

File: main.py
import config
import mod
print config.x # Prints 1

对于您的情况,也许您可​​以执行以下操作:

#foo module
SANITIZE = 'foo'
def parse():
    print SANITIZE

#foo2 module
import foo
foo.SANITIZE = 'foo2'

#test script 1
import foo
foo.parse() # Prints 'foo'

#test script 2
import foo
import foo2
foo.parse() # Prints 'foo2'

Your question looks very similar to what's described here: http://effbot.org/pyfaq/how-do-i-share-global-variables-across-modules.htm

File: config.py (your foo.py)
x = 0   # Default value of the 'x' configuration setting

File: mod.py (your foo2.py)
import config
config.x = 1

File: main.py
import config
import mod
print config.x # Prints 1

For your situation perhaps you could do the following:

#foo module
SANITIZE = 'foo'
def parse():
    print SANITIZE

#foo2 module
import foo
foo.SANITIZE = 'foo2'

#test script 1
import foo
foo.parse() # Prints 'foo'

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