在一些静态编译语言中,你会看到一些对变量类型的声明(比如C语言,Java语言中,在使用变量之前,都需要对变量的类型进行声明)。但是在python中,就可以直接对变量赋值,省略了变量类型的声明这一步。这就是Python动态类型模型的领域。
变量、对象和引用
- 变量创建:当在使用变量赋值时,第一次赋值一个变量名就会产生新的变量名,当之后的赋值操作,就会改变这个变量。
- 变量类型:变量永远不会有类型的约束,之所以变量有类型,是因为变量所指向的对象有类型,而不是变量本身有类型。变量只是在一个时间段内,引用了一个特定类型的对象而已。
- 变量使用:变量在表达式中被使用时,会被立即替换成为它所引用的对象。任何一个变量都必须在被使用之前被赋值。否则在使用时,引用空变量会产生错误。
Python中所谓的对象类型是与对象相关联的,而不是编程时常见的变量。如果将变量名和对象划分开来,动态类型是很容易理解的。
>>> a=9
这里Python程序执行就有三个大概步骤:
- 创建一个对象“3”。
- 创建一个变量名a(如果变量没有被创建的话)。
- 将变量a和对象“3”链接起来。
实际的效果就像图下的结构所示:
从变量到对象的链接,在Python中称为引用,在内存中以指针的形式出现。
从技术层面上来讲就是:
- 变量是一个系统表的元素,拥有指向对象的连接的空间,变量名没有类型
- 对象是分配的一块内存,有足够的空间来表示它所代表的值
- 引用是自动形成的从变量到对象的指针
Python内部上,缓存了不变的对象,后面新变量可以对相同的对象进行复用。从技术来讲,每个对象都有两个标准的头部信息:一个类型标志符去标识这个对象类型,以及一个引用的计数器,记载着引用这个对象的变量个数,这决定了Python是否需要对这个对象进行内存收回。
对象的垃圾收集
在python中,如果一个变量被赋予了一个新的对象,那么这个变量之前所引用的旧对象就会被回收(如果该对象也没有被其它变量引用时),这种自动回收对象空间的技术就是垃圾收集。原来无用的旧对象会被放入自由内存空间池,等待被后来的对象使用。
在技术上,Python的每个对象都会有头部信息,其中计数器就记载这引用该对象的变量数量,当数字变为零时,Python就会对该对象内存空间进行回收。可以使用sys.getrefcount来显示对象被引用的数量。
>>> help(sys.getrefcount)
Help on built-in function getrefcount in module sys:
getrefcount(...)
getrefcount(object) -> integer
Return the reference count of object. The count returned is generally
one higher than you might expect, because it includes the (temporary)
reference as an argument to getrefcount().
>>> a="lxm"
>>> sys.getrefcount("lxm")
3
>>> b="lxm"
>>> sys.getrefcount("lxm")
4
>>> c=b
>>> sys.getrefcount("lxm")
5
>>>
这种回收机制的好处就是让python更加简洁,与c/c++语言相比,少了很多基础代码。
共享引用
当两个变量都引用相同的对象时,它们都会指向相同的内存空间,这在python中叫做共享引用。
>>> a=3
>>> b=a
>>>
原处修改
当有共享引用时,就需要考虑如果对象改变了,那么指向该对象的所有变量都会变化。(当然有些对象类型(数字,字符串,元组)具有不可变性,这样的问题就不存在)。
>>> a=[1,2,3,4]
>>> b=a
>>> a=12 #变量a重新指向了新的
>>> a
12
>>> b
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> a=b
>>> a
[1, 2, 3, 4]
>>> a.pop() #列表具有可变性,当对象改变时,指向该对象的变量b也会随之一起改变。
4
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>>
如何使两个变量指向不同内存空间的相同对象呢?可以使用分片来达到“拷贝”的效果:
>>> a
[1, 2, 3]
>>> b=a[:]
>>> b
[1, 2, 3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b #此时变量b与a不是指向同样的内存空间了,它们指向的是两个不同的内存空间。分片效果实现了拷贝作用
[1, 2, 3]
>>>
其它类型对象(不能使用切片实现拷贝的)可以使用Python内置copy()方法实现拷贝(需要导入)。
import copy
x=copy.copy(Y)
x=copy.deepcopy(Y) #可以拷贝对象内嵌套的对象。
对象相等或相同
变量是否是指向的同一个对象,可以用is 表达式来判断:
>>> a=[1,2,3,4]
>>> b=[1,2,3,4]
>>> a is b #两个不同的内存空间,即两个变量指向的不同对象
False
>>> a == b
True
>>> a.pop()
4
>>> a
[1, 2, 3]
>>> b
[1, 2, 3, 4] #指向的对象不同,变量b不会随之改变
>>> a=b
>>> a is b #两个变量都指向相同的对象
True
>>> a == b
True
>>>
总结
python中的动态类型,没有了对变量类型的约束,让Python更加灵活,在开发时更加得心应手。对动态类型的了解,对Python的深入理解有更多帮助。