玩弄id().开始查看不同对象中相同属性的地址.但是现在我觉得这没关系.下到代码:
class T(object):
pass
class N(object):
pass
Run Code Online (Sandbox Code Playgroud)
首次测试(在交互式控制台中):
n = N()
t = T()
id(n)
# prints 4298619728
id(t)
# prints 4298619792
Run Code Online (Sandbox Code Playgroud)
实际上,这并不奇怪.n.__class__不同于t.__class__它似乎很明显它们不可能是同一个对象.是__class__的只是在这个时候这些对象之间的区别?假设不,如:
>>> n1 = N()
>>> n2 = N()
>>> id(n1) == id(n2)
False
Run Code Online (Sandbox Code Playgroud)
或者,Python是否只是创建单独的对象,即使它们完全相同,在内容方面,而不是分配名称n1,n2首先,在同一个对象(在内存中),并在任何一个n1或被n2修改时重新分配?为什么这样?我明白这可能是一个常规,优化,情绪,低级问题的问题(不要饶恕我),但我仍然很好奇.
现在,和以前一样的类,T()&N()- 在shell中一个接一个地执行:
>>> id(N())
4298619728
>>> id(N())
4298619792
>>> id(N())
4298619728
>>> id(N())
4298619792
Run Code Online (Sandbox Code Playgroud)
为什么玩杂耍?
但这里出现了奇怪的部分.同样,相同的类,shell:
>>> id(N()), id(T())
(4298619728, 4298619728)
>>> id(N()), id(T())
(4298619728, 4298619728)
>>> id(N()), id(T())
(4298619728, 4298619728)
Run Code Online (Sandbox Code Playgroud)
不仅杂耍停止,而且N()和T()似乎是同一个对象.因为它们不可能,所以我理解这是在整个陈述结束之前调用N()之后被破坏的任何回报.id()
我意识到这可能是一个难以回答的问题.但是我希望有人可以告诉我我在这里观察到的是什么,我的理解是否正确,分享一些关于解释器内部工作和记忆管理的黑暗魔法,或者可能指出一些关于这个主题的好资源?
感谢您抽出宝贵时间.
你问了很多问题.我会尽我所能回答其中的一些,希望你能够弄清楚其余部分(询问你是否需要帮助).
id>>> n1 = N()
>>> n2 = N()
>>> id(n1) == id(n2)
False
Run Code Online (Sandbox Code Playgroud)
这表明Python每次调用对象构造函数时都会创建一个新对象.这是有道理的,因为这正是你所要求的!如果你只想分配一个对象,但给它两个名字,那么你可以这样写:
>>> n1 = N()
>>> n2 = n1
>>> id(n1) == id(n2)
True
Run Code Online (Sandbox Code Playgroud)
您继续问为什么Python没有为对象分配实现写时复制策略.那么,每次调用构造函数时构造对象的当前策略是:
此外,写入时复制的用例并不引人注目.如果创建了许多相同的对象并且从不修改它,它只会节省存储空间.但在那种情况下,为什么要创建许多相同的对象?为什么不使用单个对象?
在CPython中,id一个对象是(秘密!)它在内存中的地址.见函数builtin_id中bltinmodule.c,线路907.
您可以通过使用__init__和__del__方法创建一个类来研究Python的内存分配行为:
class N:
def __init__(self):
print "Creating", id(self)
def __del__(self):
print "Destroying", id(self)
>>> id(N())
Creating 4300023352
Destroying 4300023352
4300023352
Run Code Online (Sandbox Code Playgroud)
您可以看到Python能够立即销毁对象,这允许它回收空间以供下次分配重用.Python使用引用计数来跟踪每个对象有多少引用,当没有对对象的引用时,它会被销毁.在同一语句的执行中,相同的内存可能会被重复使用多次.例如:
>>> id(N()), id(N()), id(N())
Creating 4300023352
Destroying 4300023352
Creating 4300023352
Destroying 4300023352
Creating 4300023352
Destroying 4300023352
(4300023352, 4300023352, 4300023352)
Run Code Online (Sandbox Code Playgroud)
我恐怕无法重现您展示的"杂耍"行为(交替创建的对象获得不同的地址).你能提供更多细节,比如Python版和操作系统吗?如果你上课,你会得到什么结果N?
好吧,如果我让我的类N继承,我可以重现杂耍object.
我有一个关于为什么会发生这种情况的理论,但是我还没有在调试器中检查它,所以请带上一点点盐.
首先,您需要了解Python内存管理器的工作原理.阅读完毕obmalloc.c并在完成后回来.我会等.
...
都明白了吗?好.所以现在你知道Python通过按大小将它们分类到池来管理小对象:每个4 KiB池包含小范围的对象,并且有一个空闲列表可以帮助分配器快速找到下一个对象的槽.分配.
现在,Python交互式shell也在创建对象:例如,抽象语法树和编译的字节代码.我的理论是,当N一个新式的类时,它的大小是这样的,它与交互式shell分配的其他对象进入同一个池.所以事件的顺序看起来像这样:
用户输入 id(N())
Python 为刚刚创建的对象在池P中分配一个插槽(调用此插槽A).
Python销毁对象并将其槽返回到池P的空闲列表.
交互shell分配了一些对象,调用它Ø.这恰好是进入池P的正确大小,因此它获得刚刚释放的插槽A.
用户id(N())再次进入.
Python 为刚创建的对象在池P中分配一个槽.槽甲已满(仍含有对象ö),所以它变得槽乙代替.
交互式shell忘记了对象O,因此它被销毁,并且插槽A被返回到池P的空闲列表.
您可以看到这解释了交替行为.在用户键入的情况下id(N()),id(N()),交互式shell没有机会在两个分配之间粘贴其桨,因此它们都可以进入池中的相同插槽.
这也解释了为什么旧式对象不会发生这种情况.据推测,旧式对象的大小不同,因此它们位于不同的池中,并且不与交互式shell正在创建的任何对象共享插槽.
请参阅pythonrun.c详细信息,但基本上是交互式shell:
读取您的输入并分配包含您的代码的字符串.
调用解析器,该解析器构造描述代码的抽象语法树.
调用编译器,它构造编译的字节代码.
调用赋值器,为堆栈帧,局部变量,全局变量等分配对象.
我不知道究竟哪个对象应该归咎于"杂耍".不是输入字符串(字符串有自己的专用分配器); 而不是抽象语法树(它在编译后被抛弃).也许它是字节码对象.
文档说明了一切:
id(object):返回对象的"标识".这是一个整数(或长整数),保证在该生命周期内该对象是唯一且恒定的.具有非重叠生存期的两个对象可以具有相同的id()值.
无论何时调用构造函数,都会创建一个新对象.该对象的id与当前存活的任何其他对象的id不同.
>>> n1 = N()
>>> n2 = N()
>>> id(n1) == id(n2)
False
Run Code Online (Sandbox Code Playgroud)
两个对象的"内容"无关紧要.它们是两个不同的实体; 他们会得到不同的ID似乎是完全合乎逻辑的.
在CPython中,id只是内存地址.它们确实被回收:如果一个对象被垃圾收集,将来某个时候创建的另一个对象可能获得相同的id.这是您在重复id(N()), id(T())测试中看到的行为:由于您没有保留对新创建的对象的引用,因此解释器可以自由地收集它们并重用它们的ID.
ids的回收显然是一个实施/平台人工制品,不应该依赖.
| 归档时间: |
|
| 查看次数: |
428 次 |
| 最近记录: |