<p>你问了很多问题。我会尽我所能回答其中的一些问题,希望你能找出其余的问题(如果需要帮助,可以询问)。在</p>
<h3>第一个问题:解释<code>id</code></h3>的行为
<pre><code>>>> n1 = N()
>>> n2 = N()
>>> id(n1) == id(n2)
False
</code></pre>
<p>这表明Python每次调用对象构造函数时都会创建一个新对象。这是有道理的,因为这正是你所要求的!如果您只想分配一个对象,但是给它两个名称,那么您可以写下:</p>
^{pr2}$
<h3>第二个问题:为什么不写就抄?</h3>
<p>您接着会问Python为什么不为对象分配实现一个写时拷贝策略。现在的策略是,每次调用构造函数时构造一个对象,是:</p>
<ol>
<li>实施简单</li>
<li>明确(完全按照你的要求去做)</li>
<li>易于记录和理解。在</li>
</ol>
<p>另外,写时拷贝的用例也不引人注目。它只在创建了许多相同的对象且从未被修改的情况下保存存储。但既然如此,为什么要创建许多相同的对象呢?为什么不使用一个对象呢?在</p>
<h3>第三个问题:解释分配行为</h3>
<p>在CPython中,对象的<code>id</code>是(秘密地!)它在内存中的地址。请参见<a href="http://hg.python.org/cpython/file/f8e7fe70c075/Python/bltinmodule.c#l907" rel="noreferrer">^{<cd4>}, line 907</a>中的函数<code>builtin_id</code>。在</p>
<p>通过使用<code>__init__</code>和<code>__del__</code>方法生成一个类,可以研究Python的内存分配行为:</p>
<pre><code>class N:
def __init__(self):
print "Creating", id(self)
def __del__(self):
print "Destroying", id(self)
>>> id(N())
Creating 4300023352
Destroying 4300023352
4300023352
</code></pre>
<p>您可以看到Python能够立即销毁对象,这使它能够回收空间,以便在下一次分配时重新使用。Python使用<a href="http://en.wikipedia.org/wiki/Reference_counting" rel="noreferrer">reference counting</a>来跟踪每个对象有多少个引用,当不再有对对象的引用时,它就会被销毁。在同一语句的执行过程中,同一内存可能会被多次重复使用。例如:</p>
<pre><code>>>> id(N()), id(N()), id(N())
Creating 4300023352
Destroying 4300023352
Creating 4300023352
Destroying 4300023352
Creating 4300023352
Destroying 4300023352
(4300023352, 4300023352, 4300023352)
</code></pre>
<h3>第四个问题:解释“杂耍”</h3>
<p><s>恐怕我无法重现您所展示的“杂耍”行为(交替创建的对象获得不同的地址)。你能提供更多的细节吗,比如Python版本和操作系统?如果你使用我的类<code>N</code>,你会得到什么结果?</s></p>
<p>好的,如果我让我的类<code>N</code>从<code>object</code>继承,我可以重现这种杂耍。在</p>
<p>我有一个关于为什么会发生这种情况的理论,但我还没有在调试器中检查过,所以请稍加注意。在</p>
<p>首先,您需要了解一下Python的内存管理器是如何工作的。通读<a href="http://hg.python.org/cpython/file/f8e7fe70c075/Objects/obmalloc.c" rel="noreferrer">^{<cd10>}</a>,看完后再回来。我等一下。在</p>
<p>。。。在</p>
<p>明白了吗?很好。现在您知道Python通过按大小将小对象分类到池中来管理小对象:每个4kib池包含大小范围较小的对象,并且有一个空闲列表来帮助分配器快速为下一个要分配的对象找到一个插槽。在</p>
<p>现在,Python交互式shell也在创建对象:例如抽象语法树和编译的字节码。我的理论是,当<code>N</code>是一个新样式的类时,它的大小与交互shell分配的其他对象进入同一个池中。事件的顺序是这样的:</p>
<ol>
<li><p>用户输入<code>id(N())</code></p></li>
<li><p>Python在pool<em>p</em>中为刚刚创建的对象分配一个槽(将这个槽称为<em>a</em>)。</p></li>
<li><p>Python销毁对象并将其槽返回到pool<em>p</em>的空闲列表中。</p></li>
<li><p>交互式shell分配一些对象,称之为<em>O</em>。这正好是进入pool<em>P</em>的正确大小,因此它得到刚刚释放的slot<em>A</em>。</p></li>
<li><p>用户再次输入<code>id(N())</code>。</p></li>
<li><p>Python在pool<em>p</em>中为刚刚创建的对象分配一个插槽。Slot<em>A</em>已满(仍包含object<em>O</em>),因此它取而代之的是Slot<em>B</em>。</p></li>
<li><p>交互shell忘记了object<em>O</em>,因此它被销毁,而slot<em>A</em>将返回池<em>P</em>的空闲列表。</p></li>
</ol>
<p>你可以看到这解释了交替行为。在用户输入<code>id(N()),id(N())</code>的情况下,交互式shell没有机会在两个分配之间插入oar,因此它们可以在池中的同一个槽中运行。在</p>
<p>这也解释了为什么旧样式的对象没有发生这种情况。假设旧样式的对象大小不同,所以它们放在不同的池中,不会与交互式shell创建的任何对象共享插槽。在</p>
<h3>第五个问题:交互式shell可能分配哪些对象?</h3>
<p>有关详细信息,请参见<a href="http://hg.python.org/cpython/file/5c7520e02d5a/Python/pythonrun.c" rel="noreferrer">^{<cd15>}</a>,但基本上是交互式shell:</p>
<ol>
<li><p>读取输入并分配包含代码的字符串。</p></li>
<li><p>调用解析器,它构造描述代码的抽象语法树。</p></li>
<li><p>调用编译器,编译器构造编译后的字节码。</p></li>
<li><p>调用计算器,它为堆栈帧、局部变量、全局变量等分配对象。</p></li>
</ol>
<p>我不知道究竟是哪一个物体造成了“杂耍”。不是输入字符串(字符串有自己的专用分配器);也不是抽象语法树(它在编译后被丢弃)。可能是字节码对象。在</p>