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)
在堆上创建一个整数对象。
- 大量内置函数将根据需要创建新对象,并从调用中返回这些对象
class
、def
语句和 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
是其中最突出的。
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)
在堆上创建一个整数对象。 - 大量内置函数将根据需要创建新对象,并从调用中返回这些对象
class
、def
语句和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
是其中最突出的。