在Python中,一切都是指针。
一:对象三特性
所有的Python对象都有三个特性:身份,类型和值。
身份:每一个对象都有一个唯一的身份标识,任何对象的身份标识可以使用内建函数id()来得到。它可以被认为是该对象的内存地址。
类型:对象的类型决定了该对象可以保存什么类型的值,可以进行什么样的操作,以及遵循什么样的规则。可以用内建函数type()查看Python对象的类型。
值:对象表示的数据项。
上面三个特性在对象创建的时候就被赋值,除了值之外,其它两个特性都是只读的。如果对象支持更新操作,那么它的值就可以改变,否则它的值也是只读的。对象的值是否可以更改被称为对象的可改变性(mutability)。
二:可变对象和不可变对象
一种对对象进行分类的方式是:可变对象和不可变对象。
可变对象允许他们的值被更新,而不可变对象则不允许他们的值被更改。下表列出了支持更新和不支持更新的类型:
数值和字符串对象是不可改变的。尽管通过给数字对象(重新)赋值,可以“更新”一个数值对象。之所以给更新这两个字加上引号,是因为实际上并没有更新该对象的原始数值。
因为数值对象是不可改变对象。Python的对象模型与常规对象模型有些不同。你所认为的更新实际上是生成了一个新的数值对象,并得到它的引用。
在Python中,变量像一个指针指向装变量值的盒子。 对不可改变类型来说,无法改变盒子的内容,但可以将指针指向一个新盒子。每次将另外的数字赋给变量的时候,实际上创建了一个新的对象并把它赋给变量.(不仅仅是数字,对于所有的不可变类型,都是这么回事)。见下例:
x = 'Pythonnumbers and strings'
x = 'are immutable?!? What gives?'
i = 0
i = i + 1
上面的例子中,事实上是一个新对象被创建,然后它取代了旧对象。就是这样。
新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。可以通过内建函数id()来确认对象的身份在两次赋值前后发生了变化。下面在上面的例子里加上id()调用,就会清楚的看到对象实际上已经被替换了:
>>> x = 'Python numbers andstrings'
>>> id(x)
23391696
>>> x = 'are immutable?!? Whatgives?'
>>> id(x)
22768232
>>> a=1
>>> id(a)
23048160
>>> a = a + 1
>>> id(a)
23048148
可变对象,比如列表,可以被修改而无须替换原始对象,看下面的例子:
>>> aList = ['ammonia', 83, 85,'lady']
>>> id(aList)
23903848
>>> id(aList[2])
23049136
>>> id(aList[3])
23898816
>>> aList[2] = aList[2] + 1
>>> aList[3] = 'stereo'
>>> aList
['ammonia', 83, 86, 'stereo']
>>> id(aList)
23903848
>>> id(aList[2])
23049124
>>> id(aList[3])
23898880
注意列表的值不论怎么改变,列表的 ID 始终保持不变。但是列表中的元素的ID却发生了变化。
三:变量
在Python中,无需显式变量声明语句,变量在第一次被赋值时自动声明。和其他大多数语言一样,变量只有被创建和赋值后才能被使用。
Python中不但变量名无需事先声明,而且也无需类型声明。对象的类型和内存占用都是运行时确定的。尽管代码被编译成字节码,Python仍然是一种解释型语言。在创建----也就是赋值时,解释器会根据语法和右侧的操作数来决定新对象的类型。
在对象创建后,一个该对象的引用会被赋值给左侧的变量。
四:引用
在Python语言中,赋值并不是直接将一个值赋给一个变量,对象是通过引用传递的。在赋值时,不管这个对象是新创建的,还是一个已经存在的,都是将该对象的引用(并不是值)赋值给变量。
要保持追踪内存中的对象,Python使用了引用计数这一简单技术。也就是说Python内部记录着所有使用中的对象各有多少引用。
当对象被创建时,引用计数置为1,当这个对象不再需要时,也就是这个对象的引用计数变为0时,它被垃圾回收。
1:增加引用计数
当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为1。
当同一个对象(的引用)又被赋值给其它变量时,或作为参数传递给函数,方法或类实例时, 或者被赋值为一个窗口对象的成员时,该对象的一个新的引用就被创建,该对象的引用计数自动加1。
以下声明:
x = 3.14
y = x
语句 x = 3.14 创建了一个浮点数对象并将其引用赋值给x。 x是第一个引用,因此,该对象的引用计数被设置为1。语句 y=x 创建了一个指向同一对象的别名y。见下图:
事实上并没有为Y创建一个新对象,而是该对象的引用计数增加了1 次(变成了2)。这是对象引用计数增加的方式之一。
还有一些其它的方式也能增加对象的引用计数,比如该对象作为参数被函数调用或这个对象被加入到某个容器对象当中时,比如下面的例子:
>>> x=3.14
>>> y=x
>>> id(x)
19411336
>>> id(y)
19411336
>>> def printid(a):
... print id(a)
...
>>> printid(x)
19411336
>>> atuple=(1, x, 2)
>>> id(atuple[1])
19411336
>>> alist=[1,x,2]
>>> id(alist[1])
19411336
>>> alist = [1,2,3]
>>> blist = alist
>>> alist.append(4)
>>> blist
[1, 2, 3, 4]
2:del 语句
该语句会删除对象的一个引用,例如,在上例中执行del y 会产生两个结果:从现在的名字空间中删除 y; x的引用计数减一
引申一步,执行 del x 会删除该对象的最后一个引用,也就是该对象的引用计数会减为0,这会导致该对象从此“无法访问”或“无法抵达”。 从此刻起,该对象就成为垃圾回收机制的回收对象。 注意任何追踪或调试程序会给一个对象增加一个额外的引用,这会推迟该对象被回收的时间。
3:减少引用计数
当对象的引用被销毁时,引用计数会减小。最明显的例子就是当引用离开其作用范围时,这种情况最经常出现在函数运行结束时,所有局部变量都被自动销毁,对象的引用计数也就随之减少。
当变量被赋值给另外一个对象时,原对象的引用计数也会自动减1:
foo = 'xyz'
bar = foo
foo = 123
当字符串对象"xyz"被创建并赋值给foo时,它的引用计数是1. 当增加了一个别名 bar时,引用计数变成了2. 不过当foo被重新赋值给整数对象123 时,xyz 对象的引用计数自动减1,又重新变成了1。
其它造成对象的引用计数减少的方式包括:使用 del 语句删除一个变量,或者当一个对象被移出一个窗口对象时,或该容器对象本身的引用计数变成了0 时。
4:垃圾回收
不再被使用的内存会被一种称为垃圾收集的机制释放。虽然解释器跟踪对象的引用计数,但垃圾收集器负责释放内存。垃圾收集器是一块独立代码,它用来寻找引用计数为0的对象。它也负责检查那些虽然引用计数大于0 但也应该被销毁的对象。
5:赋值
对象可以被赋值到另一个变量(通过引用)。因为每个变量都指向同一个(共享的)数据对象,只要任何一个引用发生改变,该对象的其它引用也会随之改变。
将变量名看作对象的一个指针。来看以下三个例子:
a: foo1 = foo2 = 4.3
当从值的观点看这条语句时,它表现的只是一个多重赋值,将4.3 这个值赋给了foo1和foo2 这两个变量。
这当然是对的,不过它还有另一层含义。 事实是一个值为4.3的数字对象被创建,然后这个对象的引用被赋值给foo1 和foo2, 结果就是 foo1 和foo2 指向同一个对象。如下图:
b:
foo1 = 4.3
foo2 = foo1
这个例子非常类似上一个,一个值为4.3 的数值对象被创建,然后赋给一个变量,当执行foo2 = foo1 时,foo2 被指向foo1 所指向的同一个对象,这是因为Python通过传递引用来处理对象。foo2 就成为原始值4.3 的一个新的引用。 这样foo1 和foo2 就都指向了同一个对象。示意图也和上图一样。
c:
foo1 = 4.3
foo2 = 4.3
(foo2 = 1.3 + 3.0)
这个例子有所不同。首先一个数字对象被创建,然后赋值给foo1. 然后第二个数值对象被创建并赋值给foo2. 尽管两个对象保存的是同样大小的值,但事实上系统中保存的都是两个独立的对象,其中foo1 是第一个对象的引用,foo2 则是第二个对象的引用。如下图:
对象就象一个装着内容的盒子。当一个对象被赋值到一个变量,就象在这个盒子上贴了一个标签,表示创建了一个引用。每当这个对象有了一个新的引用,就会在盒子上新贴一张标签。当一个引用被销毁时,这个标签就会被撕掉。当所有的标签都被撕掉时,这个盒子就会被回收。
这里的标签数,实际上就是引用计数。每个对象都天生具有一个计数器,记录它自己的引用次数。这个数目表示有多少个变量指向该对象。
Python提供了is 和is not运算符来测试两个变量是否指向同一个对象。象下面这样执行一个测试:
a is b
这个表达式等价于下面的表达式
id(a) == id(b)
例子如下:
>>> a = [ 5, 'hat', -9.3]
>>> b = a
>>> a is b
True
>>> a is not b
False
>>>
>>> b = 2.5e-5
>>> b
2.5e-005
>>> a
[5, 'hat', -9.3]
>>> a is b
False
>>> a is not b
True
在上面的例子中,注意到我们使用的是浮点数而不是整数。为什么这样?
整数对象和字符串对象是不可变对象,所以Python会很高效的缓存它们。这会造成我们认为Python应该创建新对象时,它却没有创建新对象的假象。看下面的例子:
>>> a = 1
>>> id(a)
8402824
>>> b = 1
>>> id(b)
8402824
>>>
>>> c = 1.0
>>> id(c)
8651220
>>> d = 1.0
>>> id(d)
8651204
在上面的例子中,a 和 b 指向了相同的整数对象,但是c 和 d 并没有指向相同的浮点数对象。
Python仅缓存简单整数和短字符串,因为它认为在Python应用程序中这些小整数会经常被用到。当我们在写作本书的时候,Python缓存的整数范围是(-1, 100),不过这个范围是会改变的,所以请不要在你的应用程序使用这个特性。
# True
a = 1
b = 1
print(a is b)
# True
a = "good"
b = "good"
print(a is b)
# False
a = "very good morning"
b = "very good morning"
print(a is b)
# False
a = []
b = []
print(a is b)
参考:
《Python核心编程》