Python的变量与对象(不可变对象与可变对象)

参考:
【Python】详解 可变/不可变对象 与 深/浅拷贝


1. 变量与对象

对象指的是内存中存储数据的实体,具有明确的类型,在 Python 中一切都是对象,包括函数。

变量作为对象的引用/别名,实质保存着所指对象的内存地址

Python 是一门动态(dynamic)强类型(strong)语言。动态类型语言即在运行期间才确定数据类型。例如,VBScript和Python 是动态类型的,因为它们是在赋值时确定变量的类型。相反,静态类型语言在编译期间就确定数据类型,这类语言大都通过要求在使用任一变量前声明其数据类型来确保类型固定,例如 Java 和 C。

>>> x = 666    # 666 是一个对象, 而 x 是指向对象 666 的一个变量, 类型相应为 int 型
>>> x
666
 
## 变量 x 可以指向任意对象, 而没有类型的前提限制, 因为动态语言变量类型可随着赋值而动态改变
 
>>> x = '666'  # 变量 x 指向新的对象 '666', 类型随之变为 string 型
>>> x
'666'

总之,在 Python 中,类型属于对象,变量本无类型,仅仅是一个对对象的引用。而变量指向对象的数据类型若发生变化,则变量的类型亦随之改变。而赋值语句改变的是变量对对象的引用,故一个变量可指向各种数据类型的对象。

2. 不可变对象与可变对象

在这里插入图片描述

2.1 不可变对象 (Immutable Objects)

不可变对象:对象相应内存中的值 不可改变,常见的有 int、float、bool、complex、string、tuple 等类型的对象。因为 Python 中的变量存放的是 对象引用,所以对不可变对象而言,尽管对象本身不可改变,但 变量对对象的引用或指向关系仍是可变的。具体而言,指向原不可变对象的变量被改变为指向新对象时,Python 会开辟一块新的内存区域,并令变量指向这个新内存 (存放新对象引用),因此 变量对对象的引用或指向关系是灵活的、可变的。例如:

i = 73   # 变量 i 指向原不可变对象 73 (变量 i 存放原对象 73 的引用)
i += 2   # 变量 i 指向新对象 75      (变量 i 存放原对象 75 的引用)

在这里插入图片描述
综上可知,不可变对象自身并未改变,而是创建了新不可变对象,改变了变量的对象引用。具体而言,原不可变对象 73 内存中的值并未改变,Python 创建了新不可变对象 75,并令变量 i 重新指向新不可变对象 75 / 保存对新对象 75 的引用,并通过 “垃圾回收机制” 回收原对象 73 的内存。

  • 垃圾回收 (garbage collection) 机制指:对处理完毕后不再需要的堆内存空间的数据对象 (“垃圾”) 进行清理,释放它们所使用的内存空间的过程。例如,C 使用 free() 函数;C++ 使用 delete 运算符;而在 C++ 基础上开发的 C# 和 Java 等,其程序运行环境会自动进行垃圾回收,以避免用户疏忽而忘记释放内存,造成 内存泄露 (memory leaky) 问题。
  • Python 通过 引用计数 (Reference Counting) 和一个 能够检测和打破循环引用的循环垃圾回收器 来执行垃圾回收。可用 gc 模块 控制垃圾回收器。具体而言,对每个对象维护一个 ob_refcnt 字段 (对象引用计数器),用于记录该对象当前被引用的次数。每当有新引用指向该对象时,该对象的引用计数 ob_refcnt +1;每当该对象的引用失效时,该对象的引用计数 ob_refcnt -1;一旦对象的引用计数 ob_refcnt = 0,该对象立即被回收,对象占用的内存空间将被自动放入 自由内存空间池,以待后用。
  • 这种引用计数垃圾回收机制的 优点 在于,能够自动清理不用的内存空间,甚至能够随意新建对象引用 (不建议) 而无需考虑手动释放内存空间的问题,故相比于 C 或 C++ 这类静态语言更“省心”。
  • 这种引用计数垃圾回收机制的缺点是需要额外空间资源维护引用计数。因此,也有很多语言如 Java 并未采用该机制。

注意,对于不可变对象,所有指向该对象的变量在内存中 共用同一个地址。这种多个变量引用同一个对象的现象叫做 共享引用。但不管有多少个引用指向它,都只有一个地址值,只有一个引用计数会记录指向该地址的引用数目。

>>> x = 0
>>> y = 0
>>> print(id(x) == id(y))
True
>>> print(x is y)
True
>>> print(id(0), id(x), id(y))  # 结果不唯一, 但一定是相同的
2424416677616 2424416677616 2424416677616 

2.2 可变对象(Mutable Objects)

可变对象:变量所指向对象的内存地址处的值可改变,常见的有 list、set、dict 等类型的对象。因此指向可变对象的变量若发生改变,则该可变对象亦随之改变,即发生 原地 (in-place) 修改。另一方面, 当可变对象相应内存中的值变化时,变量的对可变对象引用仍保持不变,即变量仍指向原可变对象。例如:

>>> m = [5, 9]  # 变量 m 指向可变对象 (list)
>>> id(m)
1841032547080
 
>>> m += [6]   # 可变对象 (list) 将随变量 m 的改变而发生原地 (in-place) 修改, 但 m 仍是其引用 (保存的内存地址 id 不变)
>>> id(m)
1841032547080

在这里插入图片描述
综上可知,可变对象随着变量的改变而改变,但变量对可变对象的引用关系仍保持不变,即变量仍指向原可变对象。例如,变量 m 先指向可变对象 [5, 9] ,然后随着变量增加元素 6,可变对象 [5, 9] 也随之在内存中增加 6,而变化前、后变量 m 始终指向同一个可变对象,保存对同一可变对象的引用。

但注意,我们也由此知道,对于 “看起来相同” 的可变对象,其内存地址是完全不同的,例如:

>>> n = [1, 2, 3]
>>> id(n)
1683653539464
 
>>> n = [1, 2, 3]
>>> id(n)
1683653609928

可见,对于两个可变对象 [1, 2, 3],二者是先后分别创建的新可变对象,虽然值相同,但内存地址完全不同。而这点有别于不可变对象,因为 所有指向不可变对象的变量在内存中共用同一个地址。

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值