Python碎片日记12——动态类型:变量&对象&引用、垃圾收集、缓存机制、循环引用、共享引用、拷贝方法(本文又称Python为什么这么强)

本文介绍了Python动态类型的概念,强调了类型与对象的关系。详细阐述了变量在赋值时创建,对象和引用的关联,以及Python如何通过垃圾收集自动管理内存。还探讨了缓存机制提高性能的方式,循环引用和共享引用可能导致的问题,以及如何通过拷贝方法避免共享引用带来的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态类型

动态类型是Python中很重要的概念,是Python灵活性和多态性的基础。
在Python中,类型并不需要提前进行声明,而是在运行中自动判定,这大大简化了编程的代码。
那么它是如何实现的呢?

变量&对象&引用

在Python中,变量是在赋值的时候才创建的。因此变量在使用之前必须先赋值。
当给一个变量赋值(对象)时,其内部是将对象引用给变量名:
在这里插入图片描述

  • 变量:是一个系统表的元素,拥有指向对象的连接的空间。
  • 对象:是分配的一块内存。
  • 引用:是自动形成的从变量到对象的指针。(谁说Python里没有指针的?大骗纸!o(´^`)o)
    从概念上讲,每次运行一个表达式,Python就创建一个新对象,并将对象和变量名通过引用连接起来。变量名不再像C语言中那样是和对象完全粘合在一起的(在c中,变量是一个不可改变的内存区域的标签),通过改变引用,变量名可以和任意对象连接起来,从代码角度看,即是变量可以任意改变类型。
    类型属于对象,而不是变量

垃圾收集

Python的便捷性的另一个表现是它不需要手动清理变量内存,这是如何做到的呢?
Python的每个对象有着更复杂的结构,除了它的值,还有两个标准的头部信息:

  • 类型标志符:用来标识这个对象的类型。对象记录了类型,变量就不需要另声明类型了。
  • 引用的计数器:用来记录该对象被引用的次数。当引用的计数器变零,即说明该对象已经不被需要,系统会将此对象占用的空间自动回收(自动放在自由内存空间池,等待后来的对象使用)。这种技术叫做垃圾收集。
    想要查看一个对象被引用了多少次,可以使用sys块中的getrefcount()函数获得:
import sys
sys.getrefcount(1) #查看数字1被引用的次数

缓存机制

缓存机制是Python的一个优化措施。一些小的整数和字符串在编程中被使用的频率很高,如果不停地创建和回收会降低性能。为此,Python将这些对象放在缓存中(而不是真正的、新的内存块),这些对象创建之后可以被多次引用而不再被回收,提高了运行的效率。

循环引用

变量引用了对象,那么如果对象再包含了其他引用,就有可能造成循环引用。最简单的例子就是列表:

a = [1,2,3]
a.append(a)
a #结果为[1, 2, 3, [...]]

这里面列表变量a引用的对象里包含了本身,造成了循环引用,因此输出结果直接用[…]代替,因为这是无限循环的。(对于字典也会有相似效果)
Python的垃圾收集除了基于计数器,对于这种循环引用的对象也可以及时检测并回收。关于循环检测器的更多细节,可以查看Python库手册中的gc模块。

共享引用

当使用一个变量赋值给另一个变量时,就发生了共享引用。
在这里插入图片描述那么,当a改变之后,b是否会改变呢?这就涉及到了之前日记中写的对象的不可变性了。

  • 对于不可变类型:由于该类型是不可变的,当a重新赋值后,其指向的对象必然是另一个对象,结果如下:在这里插入图片描述a与b指向两个不同对象,二者不再使用共享引用,因此是不会干扰的。
  • 对于可变类型:由于对象是可变的,那么当a发生变化后(前提是在原对象基础上改变,比如列表加个元素这样的)它指向的对象还是原来的对象。a与b仍旧保持着原来共享引用的关系,共同引用着同一个对象。因此在a发生改变后,b也发生了改变。
a = [1,2,3]
b = a
a.append(4)
b #[1, 2, 3, 4]表面上b没有发生改变,但由于对a操作了,b也随之改变了

因此,对可变类型的共享引用操作时要格外小心
那么,如何避免这种现象呢?我们可以copy一个可变对象,这样新旧变量就不再共享引用,而是引用了两个长得一样却不同的对象,就不会出现上面的干扰现象。

拷贝方法

copy的方法可以有:

  • X.copy():使用该类型自带的复制方法。(这是浅拷贝)
  • 切片:Y = X[:] (无条件切片是浅拷贝)
  • 内置函数如list(L)
  • 使用copy模块:
import copy
X = copy.copy(Y) #浅拷贝,只拷贝一层,对于深层嵌套,依旧是引用而非复制
X = copy.deepcopy(Y) #深拷贝,全部拷贝

X = L * 4 #此处的L是拷贝
X = [L] * 4 #此处的L是引用

此外,类型的不可变性也同样解释了前面提到的循环引用的问题,为什么列表、字典可以无限循环?因为列表、字典是可变的,可以在第二次赋值时引用自身(第一次是用来初始化),而第二次赋值后的对象还是原对象,最后造成无限循环。
如果是对于不可变的类型呢?按照推理,应该是简单的值改变而不再无限循环吧。。。hhh,我并没有找到不可变类型中可以实现类似的操作,比如元组本身是不能增删改的、集合虽然能添加新项但添加的内容受限并不能是set类型,所以都无法做到引用自身变量这种操作,于是我还没找到这种情况的实例用来验证。。。总之,不可变类型的引用是十分安全的(不然也不会用缓存机制允许一个对象被不受限制地引用)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值