动态类型
动态类型是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类型,所以都无法做到引用自身变量这种操作,于是我还没找到这种情况的实例用来验证。。。总之,不可变类型的引用是十分安全的(不然也不会用缓存机制允许一个对象被不受限制地引用)。