【问题标题】:Two instances of the same Python module?同一个 Python 模块的两个实例?
【发布时间】:2014-01-03 12:59:43
【问题描述】:

我创建了一个 Python 模块,其中包含一个仅打印“a!”的函数。我打开了 Python 解释器并以 2 种不同的语法导入了模块

>>> import a
>>> from a import func
>>> func()
a!
>>> a.func()
a!

此时我将 func 更改为打印其他内容,然后再次评估

>>> func()
a!
>>> a.func()
a!

这当然是意料之中的,因为模块没有重新加载。然后我重新加载了模块并希望这两个函数都能更新,但是:

>>> reload(a)
<module 'a' from 'a.py'>
>>> a.func()
aasd!
>>> func()
a!

似乎只有 a.func 会更新。我一直认为 Python 只保留同一个模块的一个实例,但现在似乎有两个。我做了进一步的测试以验证我的声明,并在模块的顶层添加了一个打印语句,然后重新启动解释器并再次导入:

>>> import a
module imported
>>> import a
>>> from a import func

这让我更加困惑,因为我预计会看到两次“导入的模块”。我做的第三个实验是全局变量实验:

>>> import a
module imported
>>> from a import GLOBAL_VAR
>>> GLOBAL_VAR = 5
>>> a.GLOBAL_VAR
1
>>> GLOBAL_VAR
5
>>> GLOBAL_VAR is a.GLOBAL_VAR
False

所以模块只有一个实例,但里面的对象有不同的实例?怎么可能用这种行为实现Gevent's monkey patching

【问题讨论】:

  • 你引用了模块内的一个对象。该引用使该对象保持活动状态。

标签: python python-2.7 python-module python-internals


【解决方案1】:

在 Python 中,变量是名称,而不是容器。一切都是对象,一切都是引用,而变量都不是。变量引用对象,命名空间将名称映射到对象。您可以使用模块来组织代码,每个模块在第一次导入时执行。

所以在你将 a 导入为 b 之后,b 引用对象 a,a 将保持不变,除非你修改对象 a(如果 a 是可变的)。当你 reload(a) 时,你只是将一个新对象分配给一个变量,这与 b 完全无关。

>>> a = 1
>>> b = a
>>> a = 2
>>> print b
1

>>> import a
>>> from a import func
>>> id(func) 
4324575544
>>> id(a.func)
4324575544
>>> reload(a)
<module 'a' from 'a.py'>
>>> id(a.func)
4324576624
>>> id(func)
4324575544

【讨论】:

  • 如果你从a导入b,那么你做b = 2,你得到的是a.b是object_b,b指的是object_2,当然b不是a.b
  • 当你重新加载 a 时,b 也会发生变化,因为重新加载操作对象 (module) a 引用(也被 b 引用)并重新加载它。 a 的所指对象没有改变,b 的也没有改变。请放心,我已经确认了这一点。
  • 我添加更多细节,这就是我得到的,无论如何,如果它仍然困扰你,只需阅读Python源代码,你就会发现。
【解决方案2】:

我刚刚确认的是,Python 的特定会话确保特定模块只有一个实例(更具体地说是文件路径)。

证明(使用单个 Python 会话):

foobar.py:

a = 123
def foo(): print a

Python:

>>> from foobar import foo
>>> foo()
123

更改 foobar.py:

a = 1234
def foo(): print a;print a

Python:

>>> reload(foobar)
<module 'foobar' from 'c:\foobar.py'>
>>> foobar.foo()
1234
1234
>>> foo()
1234
>>> id(sys.modules[foo.__module__]) == id(sys.modules[foobar.foo.__module__])
True

导致不同输出的唯一原因是 code 对象,(foo.__globals['a']foobar.foo.__globals__['a'] 相同)

>>> id(foo.__code__) == id(foobar.foo.__code__)
False

不知道Gevent猴子补丁。

【讨论】:

    【解决方案3】:

    当您重新加载一个模块时,其中的所有函数都会重新加载到当前模块中。但是当你从一个模块中导入一个特定的函数时,它就变成了当前模块的本地函数。所以,改变一个不会影响另一个。

    看看这个:

    import math
    from math import factorial
    print locals()
    
    print id(math.factorial), id(factorial)
    
    math.factorial, factorial = 0, 1
    print id(math.factorial), id(factorial)
    
    reload(math)
    print id(math.factorial), id(factorial)
    
    from math import factorial
    print id(math.factorial), id(factorial)
    

    输出

    {'__builtins__': <module '__builtin__' (built-in)>,
     '__file__': '/home/thefourtheye/Desktop/Test.py',
     '__package__': None,
     'factorial': <built-in function factorial>, # Factorial is in the local context
     '__name__': '__main__',
     '__doc__': None,
     'math': <module 'math' (built-in)>}
    39346504 39346504
    33545712 33545688
    39346504 33545688
    39346504 39346504
    

    id在CPython实现中打印内存中对象的地址

    【讨论】:

      【解决方案4】:

      一个模块,一旦被导入,就只是另一个 python 对象。所以看到下面的例子,你的结果应该不会让你感到惊讶:

      x = SomeObject()
      x.y = 1
      a = x.y
      x.y = 2
      print(a) #a is still 1, not 2
      

      当您执行from module import name 时,会在当前命名空间中创建一个变量name,其中包含对导入事物的引用(无论是包/模块/类/任何东西)。它是以下内容的语法糖:

      import module
      name = module.name
      

      现在,当您重新加载 module 时,您显然不会更新引用 name 持有。

      关于你的第二个实验,一个模块在导入后缓存在sys.modules;后续导入将利用缓存。因此,所有直接在模块级别的代码,例如您的print,只会在第一次导入时执行。

      【讨论】:

        猜你喜欢
        • 2017-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-23
        • 1970-01-01
        相关资源
        最近更新 更多