Python的id()
函数返回对象的唯一标识符。因此,当我在我的终端时,我做了类似的事情:
>> a = 23
>> id(a)
28487496
现在,我知道python会跟踪创建的所有对象和对该对象的引用数量,当值达到0时,对象将被垃圾回收。
我想知道的是,当我做这样的事情时会发生什么:
>> id(27)
28487498
我从来没有创建过具有值27的对象,即我从未写过b=27
仍然以某种方式我得到了一个唯一的标识符。这是否意味着对象是在内存中创建的?如果是,即使这样,该对象应该有0个引用,它应该被垃圾收集。
那么,什么时候实际在Memory中创建了一个Object?
如果我错了,请告诉我。
我刚发现的另一个有趣的事情是:
>> a = 23
>> id(a)
28487496
>> id(20 + 3)
28487496
在这种情况下,Python会记住对23号本身的引用,Python如何做到这一点?
答案 0 :(得分:4)
根据需要在不同的地方创建对象。
首先,当你写
b = 27
两件事事情发生了。计算27
表达式,导致整数对象被压入堆栈,然后,作为单独的步骤,将对象分配给b
。 分配不会创建对象。
如果你这样做了:
27
仍在评估27
表达式。该对象将被创建 * ,然后再次销毁,因为引用计数再次回落为0。
这是必需的,因为你可以将该对象传递给另一个函数:
id(27)
需要某些传递给id()
函数。因此27
被添加到堆栈中,因此您可以调用该函数。
我将使用可变对象而不是整数来说明创建了一个新对象;所以不是id(27)
我会使用id([])
并要求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)
在堆上创建一个整数对象。class
,def
语句和lambda
表达式创建对象(类对象,函数和更多函数,这些都是对象)。* 小整数实际上是实习;出于性能原因,CPython保留了-5和256之间的每个整数的单个副本,因此这些对象实际上只创建一次,并在您需要的任何地方引用。见"is" operator behaves unexpectedly with integers。出于这个答案的目的,我忽略了这一点。
由于他们被实习,20 + 3
的结果会返回该单一副本,而id()
仍然会与您直接询问id(23)
时的结果相同。
还有更多实施细节;还有更多。一些字符串对象被实习(参见my answer here)。在交互式解释器中评估的代码一次编译一个顶级块,但在脚本编译中,每个范围都会编译。因为常量附加到已编译的代码对象,这意味着共享常量时存在差异。等等。
datamodel documentation中明确记录了唯一不能一直重新创建的对象,因为它们是单身人士; None
是其中最突出的。