【问题标题】:When are python objects created?python对象是什么时候创建的?
【发布时间】:2017-11-15 16:34:40
【问题描述】:

Python 的id() 函数返回对象的唯一标识符。所以当在我的终端中,我会做类似的事情:

>> 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 仍然创建对象。
  • 现在,对于小整数,有一个例外:那些是被实习的(每个都保留一个副本,出于性能和内存原因,它们不是新创建的),请参阅"is" operator behaves unexpectedly with integers
  • 重要的是要注意,进行分配不是创建对象的原因。
  • -5 到 256 的整数(Py27 特定的)被缓存,所以它们是单调的,所以每次访问其中一个时,它都不会创建新对象,但从该缓存重定向到一个。

标签: python object garbage-collection cpython python-internals


【解决方案1】:

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

开始,当你写作时

b = 27

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

如果你这样做了:

27

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

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

id(27)

需要 something 传递给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 用于创建空列表对象并将其压入堆栈,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 语法的特殊操作码时创建对象。

还有更多地方:

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

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

而且因为他们是实习生,20 + 3 的结果会返回那个单一的副本,id() 仍将与您直接请求 id(23) 时相同。

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

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

【讨论】:

  • “当你创建一个类的实例时,type.__new_ 会在堆上创建一个实例对象。”_ - 所以说@987654359 是否正确@ 负责在 Python 后台创建所有个对象?
  • @ChristianDean:它负责所有 Python 定义的类型。
  • @ChristianDean:对于 C 对象,使用type->tp_alloc()
  • 好的,感谢您的澄清。我认为 Python 和 C 级别之间存在差异。
  • @ChristianDean:在幕后,type.__new__()type_new C 函数)确实在某个地方使用了type->tp_alloc(),但是对于 Python 类型有很多细节需要注意。跨度>
猜你喜欢
  • 1970-01-01
  • 2017-09-23
  • 2012-08-18
  • 2021-08-16
  • 2013-11-24
  • 1970-01-01
  • 2016-10-07
  • 2010-11-08
  • 1970-01-01
相关资源
最近更新 更多