【问题标题】:Alter module variables in one module while it doesn't change in the original改变一个模块中的模块变量,而它在原始模块中没有改变
【发布时间】:2012-01-20 00:26:43
【问题描述】:

我正在使用 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'。有没有办法实现这种行为?

谢谢!

【问题讨论】:

  • 这段代码没有意义,foo.parse() 在任何情况下都会打印'foo',它是一个不依赖于SANITIZE的常量。
  • 对不起那个yurib,我修好了。
  • 你总是可以删除并重新加载 foo.py

标签: python scope python-module


【解决方案1】:

如果这是你的代码,那么解决方案不是依赖于模块级变量,而是以其他方式保持对象的状态。 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”属性的对象--

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

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]$ 

【讨论】:

  • 感谢 jsbueno,不幸的是这不是我的代码,我必须依赖模块级变量。 Wrapper 类方法很有趣,但正如你所说,非常 hacky 并且非常非线程安全,恐怕我的情况不会这样做
  • 模块是“单例”——因此,在任何时候更改模块上的变量都会使其线程不安全。我能想到的另一种方法是创建一个“复印机”模块,该模块在导入时实际上重新创建另一个现有模块的类、属性和实例 - 重新绑定所有函数全局变量(方法仍将作为此级别的函数访问)。
  • 是的,“复印机”模块正是我需要的,我希望这样的东西已经存在
【解决方案2】:

您的问题与此处描述的内容非常相似: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'

【讨论】:

  • 感谢 sgallen,不幸的是,这并不能完全实现我想要的。您的代码不允许我在同一脚本中在 foo 和 foo2 之间切换
猜你喜欢
  • 2011-04-01
  • 2016-04-08
  • 1970-01-01
  • 1970-01-01
  • 2012-08-27
  • 1970-01-01
  • 2014-08-18
相关资源
最近更新 更多