如何在Python中模拟指针?( 三 )


如何在Python中模拟指针?

文章插图
 
您可以看到内存布局与之前的C布局截然不同 。在这里,新创建的Python对象拥有值2337所在的内存,而不是x拥有值2337所在的内存 。Python名称x不直接拥有任何内存地址,不像C变量在内存中拥有静态插槽 。
如果您尝试为x赋新的值,可以尝试以下操作:
如何在Python中模拟指针?

文章插图
 
这里发生的事情与C的同样操作不同,但与Python中的原始绑定没有太大区别 。
这行代码:
· 创建一个新的 PyObject
· 将PyObject的typecode设置为整数
· 将PyObject的值设置为2338
· 将x指向新的PyObject
· 将新的PyObject引用计数增加1
· 将旧的PyObject引用计数减少1
现在在内存中,它看起来像这样:
如何在Python中模拟指针?

文章插图
 
此图有助于说明x指向对象的引用,并不像以前那样拥有内存空间 。它还表明命令“x = 2338”不是赋值,而是将名称x绑定到一个引用 。
此外,前一个对象(拥有值2337)现在位于内存中,引用计数为0,并将被垃圾收集器清理 。
您可以引入一个新名称y,就如C的示例一样:
如何在Python中模拟指针?

文章插图
 
在内存中,您将拥有一个新名称,但不一定是新对象:
如何在Python中模拟指针?

文章插图
 
现在,你可以看到并没有创建一个新的Python对象,只是创建指向同一个对象的新名称 。此外,对象的引用参数增加了1 。您可以检查对象标识来确认它们是否相同:
如何在Python中模拟指针?

文章插图
 
上面的代码表明x和y是相同的对象 。没错:y仍然是不可改变的 。
例如,您可以对有y执行以下操作:
如何在Python中模拟指针?

文章插图
 
添加调用后,将返回一个新的Python对象 。现在,内存看起来像这样:
如何在Python中模拟指针?

文章插图
 
一个新对象被创建,y现在指向新对象 。有趣的是,如果你已经将2339绑定到y,结束状态也是如此:
如何在Python中模拟指针?

文章插图
 
上述语句导致与添加相同的结束内存状态 。回顾一下,在Python中,您不需要分配变量 。而是将名称绑定到引用 。
关于Python中的预实现对象的注释现在您已经了解了如何创建Python对象并将名称绑定到这些对象,现在是时候在机器中抛出一把扳手了 。该扳手叫做预实现对象 。
假设您有以下Python代码:
如何在Python中模拟指针?

文章插图
 
如上所述,x和y这两个名字都指向同一个Python对象 。但是保存1000的Python对象并不能保证总是具有相同的内存地址 。例如,如果将两个数字相加以获得1000,则最终会得到一个不同的内存地址:
如何在Python中模拟指针?

文章插图
 
这一次,"x is y"返回False 。如果这令人困惑,别担心 。以下是执行此代码时发生的步骤:
1.创建Python对象(1000)
2.将名称分配x给该对象
3.创建Python对象(499)
4.创建Python对象(501)
5.将这两个对象一起添加
6.创建一个新的Python对象(1000)
7.将名称分配y给该对象
技术说明:只有在REPL中执行此代码时,才会执行上述步骤 。如果您采用上面的示例,将其粘贴到一个文件中,然后运行该文件,那么您会发现"x is y"将返回True 。
这是因为编译器很聪明 。CPython编译器尝试进行称为窥孔优化的优化,这有助于尽可能地保存执行步骤 。有关详细信息,您可以查看CPython的窥孔优化器源代码 。
这不是浪费吗?嗯,是的,这是你为Python所有巨大好处付出的代价 。您永远不必担心如何清理这些中间对象,甚至都不需要知道它们存在!令人高兴的是,这些操作相对较快,并且直到现在你都不需要去理解这些细节 。
Python核心开发人员也睿智地注意到了这种浪费,并决定进行一些优化 。这些优化产生了令新手感到惊讶的行为:


推荐阅读