我对Python子解释器初始化(来自Python / C API)和Python id()
函数的内部工作有疑问。更准确地说,关于WSGI Python容器中的全局模块对象的处理(例如在Apache上使用nWSx和mod_wsgi的uWSGI)。
以下代码在两个提到的环境中都按预期工作(隔离),但我无法向自己解释为什么id()
函数总是为每个变量返回相同的值,无论执行它的进程/子解释器如何。
from __future__ import print_function
import os, sys
def log(*msg):
print(">>>", *msg, file=sys.stderr)
class A:
def __init__(self, x):
self.x = x
def __str__(self):
return self.x
def set(self, x):
self.x = x
a = A("one")
log("class instantiated.")
def application(environ, start_response):
output = "pid = %d\n" % os.getpid()
output += "id(A) = %d\n" % id(A)
output += "id(a) = %d\n" % id(a)
output += "str(a) = %s\n\n" % a
a.set("two")
status = "200 OK"
response_headers = [
('Content-type', 'text/plain'), ('Content-Length', str(len(output)))
]
start_response(status, response_headers)
return [output]
我已经在uWSGI中使用一个主进程和2个工作程序测试了此代码;在mod_wsgi中使用带有两个进程和每个进程一个线程的deamon模式。典型的输出是:
pid = 15278
id(A)= 139748093678128
id(a)= 139748093962360
str(a)=一个
首次加载,然后:
pid = 15282
id(A)= 139748093678128
id(a)= 139748093962360
str(a)=一个
秒,然后
pid = 15278 | pid = 15282
id(A)= 139748093678128
id(a)= 139748093962360
str(a)=两个
彼此。如您所见,类和类实例的id()
(内存位置)在两个进程(上面的第一个/第二个加载)中保持相同,而同时类实例生活在一个单独的环境中(否则第二个请求将显示“两个”而不是“一个”)!
我怀疑Python文档会暗示答案:
id(object)
:返回对象的“标识”。这是一个整数(或长整数) 对于此对象的生命周期,保证唯一且恒定。二 生命周期不重叠的对象可能具有相同的
id()
值。
但如果确实是这个原因,我会对下一个声称id()
值是对象地址的声明感到不安!
虽然我很欣赏这可能只是一个Python / C API“聪明”功能解决(或者更确切地说修复)problem of caching object references (pointers) in 3rd party extension modules,我仍然发现这种行为不一致......好吧,常识。有人可以解释一下吗?
我还注意到mod_wsgi在每个进程中导入模块(即两次),而uWSGI仅为两个进程导入模块一次。由于uWSGI主进程执行导入,我想它会为子进程播放该上下文的副本。两个工作人员之后独立工作(深拷贝?),同时使用相同的对象地址。 (此外,工作人员在重新加载时会重新初始化为原始上下文。)
我为这么长的帖子道歉,但我想提供足够的细节。 谢谢!
答案 0 :(得分:2)
这很容易通过演示来解释。你看,当uwsgi创建一个新进程时,它会分叉解释器。现在,forks有一些有趣的内存属性:
import os, time
if os.fork() == 0:
print "child first " + str(hex(id(os)))
time.sleep(2)
os.attr = 'test'
print "child second " + str(hex(id(os)))
else:
time.sleep(1)
print "parent first " + str(hex(id(os)))
time.sleep(2)
print "parent second " + str(hex(id(os)))
print os.attr
输出:
child first 0xb782414cL
parent first 0xb782414cL
child second 0xb782414cL
parent second 0xb782414cL
Traceback (most recent call last):
File "test.py", line 13, in <module>
print os.attr
AttributeError: 'module' object has no attribute 'attr'
虽然对象似乎驻留在同一个内存地址中,但它们是不同的对象,但这不是python,而是os。
编辑:我怀疑mod_wsgi导入两次的原因是它通过调用python而不是forking来创建进一步的进程。 uwsgi的方法更好,因为它可以使用更少的内存。 fork的页面共享是COW(写入时复制)。
答案 1 :(得分:1)
你所要求的并不完全清楚;如果问题更具体,我会给出一个更简洁的答案。
首先,对象的id实际上 - 至少在CPython中 - 它在内存中的地址。这是完全正常的:同一进程中的两个对象不能共享一个地址,并且对象的地址在CPython中永远不会改变,因此该地址作为一个id整齐地工作。我不知道这是如何违反常识的。
接下来,请注意,后端进程可能以两种截然不同的方式生成:
但是,这两种情况的最终结果都是相同的:单独的分叉流程。
那么,为什么你最终得到相同的ID?这取决于上述哪种方法正在使用中。
然而,无论哪种方式,一旦fork发生,它们就是单独的对象,在不同的上下文中。对具有相同ID的单独进程中的对象没有意义。