在开始讲引用之前,我们需要先介绍3个工具来辅助我们的理解与学习
在Python中,内置函数 id()
用于返回一个对象的内存地址。每个对象都有自己在内存中的唯一地址,通过 id()
函数可以获取这个地址。
具体地, id()
函数接受一个对象作为参数,并返回一个整数值,这个整数值就是该对象在内存中存储的地址。例如,对于同一个整数对象,不论它被赋值到哪个变量中,它在内存中的地址都不会改变,因此对这个对象调用 id()
函数返回的地址值也是相同的。
需要注意的是, id()
函数返回的地址值是一个整数,但实际上这个值只是一个标识符,并不代表对象的实际地址。由于 Python 程序是运行在虚拟机中的,因此 id()
函数返回的地址值实际上是一个虚拟地址,不同的进程或者系统可能会有不同的映射方式。因此,通常情况下我们只需要关心 id()
函数返回的地址值是否相同,而不需要关心其具体的数值。
a = 100000
print(id(a))
输出
2445063190384
在Python中,操作符is
用于比较两个对象是否是同一个对象,也就是比较它们在内存中的地址是否相同。如果两个对象是同一个对象,则返回True;否则返回False。
与操作符==
不同,操作符is
比较的是两个对象的内存地址,而==
比较的是两个对象的值是否相等。因此,在比较两个对象是否相等时,通常使用==
操作符;而在比较两个对象是否是同一个对象时,使用is
操作符。
需要注意的是,对于一些简单的对象,如整数、浮点数、字符串等等,如果它们的值相同,它们的地址也可能相同,这是因为Python的常量池机制会共享一些简单对象的内存地址。因此,对于这些对象,使用is
操作符和==
操作符可能同时返回True。但是,对于其他更复杂的对象,如列表、字典等,它们的地址不可能相同,因此使用is
操作符比较时,通常总是返回False。
常量池,也被称之为共享池。常量池是在程序编译时或解释时预先创建的一组常量对象,这些对象包括整数、浮点数、字符串等不可变类型,这些对象不会被垃圾回收机制回收。当程序使用这些常量对象时,实际上是引用了这些已经存在的对象。
常量池的好处在于,避免了重复创建相同的对象,提高了程序的运行效率和内存利用率。例如,如果程序需要使用多个整数1来做计算,那么Python只需要在常量池中检查是否存在整数1的对象,如果已经存在,则直接引用这个对象,而不是创建新的整数1对象。这可以减少内存的使用,并加速程序的执行。
值得注意的是,并非所有的不可变类型都可以缓存到常量池中。例如,由于元组等复杂对象通常由多个不同类型的对象组成,因此并不能在常量池中缓存这些对象。
Python中的常量池机制是根据对象的值来缓存对象的,而不是根据对象的类型或者标识符。也就是说,对于不同的变量,只要它们的值相同,就会被缓存为同一个对象。例如:
a = 1
b = 1
print(a is b) # 输出:True
需要注意的是,对于某些不可缓存到常量池中的对象,Python解释器可能会自动使用对象池来存储这些对象。例如,Python的字符串类型(str)可能会使用对象池来缓存长度小于等于一个字符的字符串对象。虽然这些对象并不是常量池中的对象,但它们也可以被多个变量共享,从而减少了内存的使用。例如:
a = "a"
print(id(a)) # 输出:2241416382192
b = "a"
print(id(b)) # 输出:2241416382192
对于这句话,我们再详细解释一下:
Python中的字符串(str)虽然通常不能缓存到常量池中,但是当字符串的长度小于等于一个字符时,Python解释器会自动使用一个对象池来存储这些字符串对象。对象池是一种内存池,用于存储某些小的、经常使用的、不变的对象。因为字符串是程序中经常使用的对象之一,而且有些程序中会频繁地创建、销毁、再创建小字符串对象,这样会导致大量的内存碎片和内存分配时间的浪费。为了避免这种浪费,Python的解释器在内部使用对象池来管理这些小字符串对象。
例如,当程序创建一个长度为1的字符串对象s时,Python的解释器会先检查对象池中是否已经存在一个值为s的字符串对象,如果存在,则直接返回该对象;否则,创建一个新的字符串对象,并将其加入到对象池中,从而实现对象的共享利用。当程序不再引用该对象时,该对象就会被释放,并从对象池中删除。
需要注意的是,对象池和常量池是不同的概念。常量池是程序编译/解释时预先创建的一组常量对象,包括整数、浮点数、字符串等不可变类型。而对象池是一种动态的内存管理机制,用于缓存一些小的、经常使用的、不变的对象,例如长度小于等于一个字符的字符串对象。虽然对象池中的对象不能缓存到常量池中,但它们也可以被多个变量共享,从而减少了内存的使用。但它们都属于内存池的范畴
该函数用于获取一个对象的引用计数,即对象被多少个变量引用
sys.getrefcount(object)
object:要检查引用计数的对象
下面请看一下示例代码
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 输出2,因为a本身引用了这个对象,同时函数也引用了一次
b = a
print(sys.getrefcount(a)) # 输出3,因为现在a和b都引用了这个对象
c = [a, a]
print(sys.getrefcount(a)) # 输出5,因为现在还有两个变量c[0]和c[1]也引用了这个对象
del b # 删除变量b,引用计数减1
print(sys.getrefcount(a)) # 输出4,因为现在只有a、c[0]、c[1]引用了这个对象
del c # 删除变量c,引用计数减2
print(sys.getrefcount(a)) # 输出2,因为现在只有a和函数参数引用了这个对象
在Python中,引用是指向内存空间中的对象的指针。当创建一个变量时,Python会分配一段内存空间来存储这个变量所代表的值,然后将这个变量名与该内存空间中的地址关联起来,这样就建立了一个引用。当你使用该变量时,实际上是通过该引用来访问这个内存空间中的对象,从而对其进行操作。
通俗来说,引用是指一个变量或者对象对于另一个对象的指向关系,类似于指针。
在Python中,变量实际上是一个对象的名称,它指向存储在内存中的对象。例如,在下面这个例子中,变量a
指向了一个整数对象2
:
a = 2
在这个例子中,等号=
符号实际上是赋值操作符,它将整数对象2
赋值给了变量a
。因此,变量a
实际上是一个指向整数对象2
的引用。
可以使用内置函数id()
来获取一个对象的唯一标识符,即对象的身份,例如:
print(id(a)) # 输出整数对象2的身份
不同的对象具有不同的身份,即使它们的值相同。因此,在Python中,身份是比值更重要的概念。
还可以使用关键字is
来判断两个对象是否引用同一个对象。如果两个对象的身份相同,则返回True
,否则返回False
。例如:
a = 2
b = a
print(a is b) # 输出True,因为a和b引用同一个整数对象2
c = 2
print(a is c) # 输出True,因为c引用的也是整数对象2
d = 3
print(a is d) # 输出False,因为a和d引用不同的整数对象
可以看到,变量b
和a
引用同一个整数对象2
,因此比较它们的身份结果为True
。而变量c
又引用了同一个整数对象2
,所以比较结果也为True
。而变量d
则引用了一个不同的整数对象3
,因此比较结果为False
。