Python之引用

文章目录

  • 1、知识铺垫
    • 1.1、内置函数id()
    • 1.2、操作符is
      • 1.2.1、常量池和对象池
    • 1.3、sys模块的getrefcount()函数
      • 1.3.1、作用
      • 1.3.2、语法
      • 1.3.3、注意事项:
  • 2、引用

1、知识铺垫

在开始讲引用之前,我们需要先介绍3个工具来辅助我们的理解与学习

1.1、内置函数id()

在Python中,内置函数 id() 用于返回一个对象的内存地址。每个对象都有自己在内存中的唯一地址,通过 id() 函数可以获取这个地址。

具体地, id() 函数接受一个对象作为参数,并返回一个整数值,这个整数值就是该对象在内存中存储的地址。例如,对于同一个整数对象,不论它被赋值到哪个变量中,它在内存中的地址都不会改变,因此对这个对象调用 id() 函数返回的地址值也是相同的。

需要注意的是, id() 函数返回的地址值是一个整数,但实际上这个值只是一个标识符,并不代表对象的实际地址。由于 Python 程序是运行在虚拟机中的,因此 id() 函数返回的地址值实际上是一个虚拟地址,不同的进程或者系统可能会有不同的映射方式。因此,通常情况下我们只需要关心 id() 函数返回的地址值是否相同,而不需要关心其具体的数值。

a = 100000
print(id(a))
输出
2445063190384

1.2、操作符is

在Python中,操作符is用于比较两个对象是否是同一个对象,也就是比较它们在内存中的地址是否相同。如果两个对象是同一个对象,则返回True;否则返回False。

与操作符==不同,操作符is比较的是两个对象的内存地址,而==比较的是两个对象的值是否相等。因此,在比较两个对象是否相等时,通常使用==操作符;而在比较两个对象是否是同一个对象时,使用is操作符。

需要注意的是,对于一些简单的对象,如整数、浮点数、字符串等等,如果它们的值相同,它们的地址也可能相同,这是因为Python的常量池机制会共享一些简单对象的内存地址。因此,对于这些对象,使用is操作符和==操作符可能同时返回True。但是,对于其他更复杂的对象,如列表、字典等,它们的地址不可能相同,因此使用is操作符比较时,通常总是返回False。

1.2.1、常量池和对象池

常量池,也被称之为共享池。常量池是在程序编译时或解释时预先创建的一组常量对象,这些对象包括整数、浮点数、字符串等不可变类型,这些对象不会被垃圾回收机制回收。当程序使用这些常量对象时,实际上是引用了这些已经存在的对象。

常量池的好处在于,避免了重复创建相同的对象,提高了程序的运行效率和内存利用率。例如,如果程序需要使用多个整数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的字符串对象,如果存在,则直接返回该对象;否则,创建一个新的字符串对象,并将其加入到对象池中,从而实现对象的共享利用。当程序不再引用该对象时,该对象就会被释放,并从对象池中删除。

需要注意的是,对象池常量池是不同的概念。常量池是程序编译/解释时预先创建的一组常量对象,包括整数、浮点数、字符串等不可变类型。而对象池是一种动态的内存管理机制,用于缓存一些小的、经常使用的、不变的对象,例如长度小于等于一个字符的字符串对象。虽然对象池中的对象不能缓存到常量池中,但它们也可以被多个变量共享,从而减少了内存的使用。但它们都属于内存池的范畴

1.3、sys模块的getrefcount()函数

1.3.1、作用

该函数用于获取一个对象的引用计数,即对象被多少个变量引用

1.3.2、语法

sys.getrefcount(object)

object:要检查引用计数的对象

1.3.3、注意事项:

  • 调用getrefcount()函数会增加对象的引用计数,因为函数参数自身也是一个对目标对象的引用。
  • 引用计数至少为1,因为对象至少被一个变量引用(即函数参数)。
  • 由于调用getrefcount()函数会增加引用计数,因此,在实际使用中要注意不要通过该函数来确定对象是否应该被回收,否则会产生误导。

下面请看一下示例代码

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和函数参数引用了这个对象

2、引用

在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引用不同的整数对象

可以看到,变量ba引用同一个整数对象2,因此比较它们的身份结果为True。而变量c又引用了同一个整数对象2,所以比较结果也为True。而变量d则引用了一个不同的整数对象3,因此比较结果为False

你可能感兴趣的:(python,jvm,java)