我有一个关于Python子互动初始化(来自Python/C API)和Python的内部工作的问题 id() 功能。更准确地说,关于在WSGI Python容器中处理全局模块对象(例如与nginx一起使用的UWSGI和Apache上的MOD_WSGI)。

以下代码在提到的两个环境中按预期(隔离)起作用,但我无法解释自己 为何 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() (内存位置)类和类实例的同时保持不变 两个都 流程(上面的第一个/秒加载),而同时class实例在单独的上下文中进行(否则第二个请求将显示“两个”而不是“一个”)!

我怀疑答案可能是由Python文档暗示的:

id(object):

返回对象的“身份”。这是一个整数(或长整数),保证该对象在其生命周期内是唯一且恒定的。两个具有非重叠寿命的对象可能具有相同的 id() 价值。

但是,如果确实是原因,我对下一个声明的声明感到困扰 id() 值是对象的地址!

虽然我很欣赏这一事实,这很可能只是一个解决方案的python/c api“聪明”功能(或更确切地说是 修复) 一种 第三方扩展模块中缓存对象参考(指针)的问题, ,我仍然发现这种行为与……常识不一致。有人可以解释一下吗?

我还注意到mod_wsgi在每个过程中导入模块(即 两次),虽然UWSGI仅导入模块 一次 对于这两个过程。由于UWSGI总体过程可以进行导入,因此我想它会使孩子们用该上下文的副本播种。两名工人之后独立工作(深拷贝?),同时似乎使用相同的对象地址。 (此外,工人在重新加载时被重新定为原始上下文。)

对于这么长的帖子,我深表歉意,但我想提供足够的详细信息。谢谢!

有帮助吗?

解决方案

目前尚不清楚您要问什么;如果问题更具体,我会给出一个更简洁的答案。

首先,实际上至少在cpython中,一个对象的ID在内存中的地址。这是完全正常的:在同一过程中的两个对象同时无法共享地址,并且对象的地址在CPYTHON中永远不会改变,因此该地址作为ID巧妙地工作。我不知道这是如何违反常识的。

接下来,请注意,后端过程可能以两种非常不同的方式产生:

  • 通用的WSGI后端处理程序将分叉进程,然后每个过程都会启动后端。这是简单且语言不可思议的,但是浪费了很多内存,并且浪费了时间,反复加载后端代码。
  • 更高级的后端将加载一次Python代码,然后加载服务器的叉子副本。这会导致代码仅加载一次,这要快得多,并大大减少了记忆浪费。这就是生产质量的WSGI服务器的工作方式。

但是,这两种情况的最终结果都是相同的:单独的分叉过程。

那么,您为什么要获得相同的ID?这取决于上述哪种方法正在使用。

  • 使用通用的WSGI处理程序,这仅仅是因为每个过程都在做同一件事。只要流程做同样的事情,它们倾向于最终获得相同的ID;在某个时候,他们会发挥作用,这将不再发生。
  • 在预加载后端,它正在发生,因为此初始代码仅在服务器叉之前发生一次,因此可以保证具有相同的ID。

然而, 无论哪种方式, ,一旦叉子发生,它们是单独的对象,在不同的上下文中。在具有相同ID的单独过程中,对象没有意义。

其他提示

这很容易通过演示来解释。您会看到,当Uwsgi创建一个新过程时,它会叉出解释器。现在,叉有有趣的内存属性:

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'

尽管对象似乎位于同一内存addr,但它们是不同的对象,但这不是Python,而是OS。

编辑:我怀疑MOD_WSGI两次导入的原因是它通过调用Python而不是分叉创建进一步的过程。 UWSGI的方法更好,因为它可以使用更少的内存。叉子的页面共享是牛(在写作上复制)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top