python 对象何时创建?

When are python objects created?

Python的id()函数returns对象的唯一标识符。所以当在我的终端我做这样的事情:

>> a = 23
>> id(a)
28487496

现在,我知道 python 跟踪创建的所有对象和对该对象的引用数,当值达到 0 时,该对象将被垃圾回收。

我想知道当我这样做时会发生什么:

>> id(27)
28487498

我从来没有创建一个值为 27 的对象,即我从来没有写过 b=27 仍然不知何故我得到了一个唯一的标识符。这是否意味着在内存中创建了一个对象?如果是,即使那样也应该有 0 个对该对象的引用并且它应该已经被垃圾收集了。

那么,什么时候在内存中真正创建一个对象?

如果我哪里错了,请告诉我。

我刚刚发现的另一个有趣的事情是:

>> a = 23
>> id(a)
28487496
>> id(20 + 3)

28487496

在这种情况下 Python 记住了对数字 23 本身的引用,Python 是怎么做到的?

根据需要在不同的地方创建对象。

开始,当你写作时

b = 27

两件事发生了。 27 表达式被求值,导致一个整数对象被压入堆栈,然后,作为一个单独的步骤,该对象被分配给 b赋值不创建对象

如果您这样做:

27

27 表达式仍然被计算。该对象将被创建*,然后随着引用计数再次降回 0 而再次销毁。

这是必需的,因为您可以将该对象传递给另一个函数:

id(27)

需要一些东西传递给id()函数。所以 27 被添加到堆栈中,这样你就可以调用函数了。

我将使用可变对象而不是整数来说明创建了一个新对象;所以我将使用 id([]) 而不是 id(27) 并要求 dis module 向我展示 Python 将执行的字节码:

>>> import dis
>>> dis.dis(compile('id([])', '', 'exec'))
  1           0 LOAD_NAME                0 (id)
              2 BUILD_LIST               0
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE

然后 BUILD_LIST 0 opcode is used to create the empty list object and push it onto the stack, and CALL_FUNCTION 1 调用 id 从堆栈中传入一个值,即那个列表。

我没有使用 id(27) 因为 不可变对象 如整数和元组等实际上与编译的字节码一起缓存;这些是在 Python 编译代码时创建的(或者当您从磁盘加载 .pyc 字节码缓存时):

>>> dis.dis(compile('id(27)', '', 'exec'))
  1           0 LOAD_NAME                0 (id)
              2 LOAD_CONST               0 (27)
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

注意 LOAD_CONST,它从 co_consts 结构加载数据:

>>> compile('id(27)', '', 'exec').co_consts
(27, None)

因此可以在编译时创建对象,或者在针对特定 Python 语法执行特殊操作码时创建对象。

还有更多地方:

  • 还有更多操作码,例如用于创建列表、元组、字典、集合和字符串。
  • 当您创建一个 class 的实例时,type.__new__ 将在堆上创建一个实例对象。所以 CustomClass(arg1, arg2) 创建了一个正确类型的对象。
  • 这同样适用于所有内置类型; int(somevalue) 在堆上创建一个整数对象。
  • 大量内置函数将根据需要创建新对象,并从调用中返回这些对象
  • classdef 语句和 lambda 表达式创建对象(class 对象、函数和更多函数,这些也是对象)。

* 小整数实际上是 intern 的;出于性能原因,CPython 为 -5 和 256 之间的每个整数保留一份副本,因此这些对象实际上只创建一次,并在您需要的任何地方引用。参见 "is" operator behaves unexpectedly with integers。为了这个答案的目的,我忽略了这一点。

而且因为它们是 interned 的,所以 20 + 3 returns 那个单一副本和 id() 的结果仍然和你直接请求 id(23) 一样.

还有更多的实现细节;还有更多。一些字符串对象是驻留的(参见 my answer here)。在交互式解释器中评估的代码一次编译一个顶级块,但在脚本中编译是按作用域完成的。因为常量附加到编译后的代码对象,这意味着在何时共享常量方面存在差异。等等等等

您唯一可以依赖的不会一直被重新创建的对象在 datamodel documentation 中被明确记录为单例; None 是其中最突出的。