Python中只有一个赋值模型
一、缺少类型声明语句的情况
在Python中,类型是在运行过程中自动决定的,而不是通过代码声明。这意味着没有必要事声明变量。只要记住,这个概念实质上对变量,对象和它们之间的关系都适用。那么这个概念也容易理解并掌握。
1、变量,对象和引用
变量创建:一个变量,当代码第一次给它赋值时它就被创建了。之后的赋值将会改变已创建的变量名的值。Python在代码运行之前先检测变量名,可以当成是最初的赋值创建变量。
变量类型:变量永远不会有任何的它关联的类型信息或约束。类型的概念是存在于对象中而不是变量中。变量原本是通用的。它只是在一个特定的时间点,简单地引用了一个特定的对像而已。
变量的使用:当变量出现在表达式中时,它会马上被当前引用的对像所代替,无论这个对象是什么类型。
此外,所有的变量都必须在其使用前明确地赋值。使用未赋值的变量会产生错误。
>>>a=3
在概念上说,Python将执行三个不同的步骤去完成这个请求。
1)、创建一个对象来代表值3
2)、创建一个变量a,如果它还没有创建的话
3)、将变量与新的对象3连接
在python中从变量到对象的连接称作引用。引用是一种关系,以内存中的指针形式实现。
*变量 是一个系统表的元素,拥有指向对象的连接空间。
*对象 是被分配的一块内存,有足够的空间去表现它们所代表的值。
*引用 是自动形成的从变量到对象的指针。
每一个对象都用两个标准的头部信息:一个类型标志符去标识这个对象的类型,以及一个引用的计数器,用来决定是不是可以回收这个对象。
2、类型属于对象,而不是变量
Python中的类型是与对象相关联的,而不是和变量关联。
变量没有类型,变量指向对象。对象有类型,知道自己的类型,每个对象都包含了一个头部信息,其中标记了这个对象的类型。
3、对象的垃圾收集
对象生命结束时发生了什么变化?
每当一个变量名被赋与了一个新的对象,之前的那个对象占用的空间就会被收回(如果它没有被其他变量名和对象所引用).这种自动回收对象空间的技术称作垃圾收集。
在内部,Python是通过保持用每个对象中的计数器记录引用指到这个对象上的次数来完成这一功能。一旦(并精确在同一时间)这个计数器被设置为零,这个
对象的内存空间就会自动收回。垃圾收集最直接的,可感受到的好处就是这意味着可以在脚本中任意使用对象而不需要考虑释放内存空间。
>>> x=42
>>> id(x)
674748828
>>> x="diege"
>>> id(x)
676367648
二、共享引用
上面所讲都是单个变量被赋值引用了多个对象的情况。现在,在交互模式下,引入另一个变量,并看一下变量名和对象的变化。
>>> a=10
>>> b=a
>>> id(a)
674749212
>>> id(b)
674749212
第二行会使用python创建变量b。使用的是变量a,并且它在这里没有被赋值,所以它被替换成其应用的对象10,从而b也成为这个对象的一个引用。实际
效果就是变量a和b都引用相同的对象(也就是说指向了相同的内存空间。在Python中称作是共享引用--多个变量名应用了同一个对象。)
>>> a=10
>>> b=a
>>> a='diege'
>>> id(a)
676367648
>>> id(b)
674749212
变量a改变了,但是不影响变量b.这完全可以说明变量b是指向对象10内存空间的。
在ptyhon中,变量总是一个指向对象的指针,而不是可以改变的内存区域的标签。给一变量赋一个新的值,并不是替换了原始的对象,而是让这个变量去引用完全不同的一个对象。实际的效果就是对一个变量赋值,仅仅会影响那个被赋值的变量。
1、共享引用和在原处修改
有一些对象和类型确实会在实地改变对象。例如,在一个列表中对一个偏移进行赋值确实会改变这个列表对象,而不是生成一个新的列表对象。
>>> T1=[11,12,13]
>>> T2=T1
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T1=22
>>> T1
22
>>> T2
[11, 12, 13]
这个和先前一样T1改变了T2没有改变,T2改变也不影响T1
>>> T1=[11,12,13]
>>> T2=T1
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T2[1]=33
>>> T1
[33, 12, 13]
>>> T2
[33, 12, 13]
发现T2改变了,T1也跟这改变了.
同样T1改变了,T2也改变了
>>> T1[1]=99
>>> T2
[33, 99, 13]
>>> T1
[33, 99, 13]
这里T1没有改变,改变了T1所引用对象的一个元素。这类修改会覆盖列表对象中的某部分。因为这个列表对象是与其他对象共享的(被其他对象引用),那么一个像这样在原处的改变不仅仅会对T1有影响。必须意识到当做了这样的修改,它会影响程序的其他部分。
如果不想要这样的现象发生,需要Python拷贝对象,而不是创建引用。方法包括内置列表函数以及标准库的copy模块,最常用的办法就是从头到尾的分片T1[:]
>>> T1=[11,12,13]
>>> T2=T1[:]
>>> T1
[11, 12, 13]
>>> T2
[11, 12, 13]
>>> T1[0]=99
>>> T1
[99, 12, 13]
>>> T2
[11, 12, 13]
>>> id(T1)
676366604
>>> id(T2)
675542060
T1和T2指向不同的对象,所以不会相互影响。
注意:这种分片技术不会引用在其他的可变的核心类型上(字典,因为它们不是序列),对字典应该使用D.copy()方法.而且,注意标准库中的copy模块有一个通用的拷贝任意对象的调用,也有一个拷贝嵌套对象的结构的调用.
>>> X={'name':'diege','age':28}
>>> import copy
>>> Y=copy.copy(X)
>>> X
{'age': 28, 'name': 'diege'}
>>> Y
{'age': 28, 'name': 'diege'}
>>> id(X)
676370468
>>> id(Y)
676414436
>>> X={'name':{'FirstName':'diege','LastName':'wangkaijin'},'age':28}
>>> X
{'age': 28, 'name': {'LastName': 'wangkaijin', 'FirstName': 'diege'}}
>>> Y=copy.copy(X)
>>> Y
{'age': 28, 'name': {'LastName': 'wangkaijin', 'FirstName': 'diege'}}
>>> Z=copy.deepcopy(X)
>>> Z
{'age': 28, 'name': {'LastName': 'wangkaijin', 'FirstName': 'diege'}}
2、共享引用和相等
>>> x=33
>>> x='diege'
因为Python缓存并复用了小的整数和小的字符串,就像前文提到的那样,这里对象33也许不像前期所说的被收回,相反,它将可能仍保持在一个系统表中,
等待下一次你的代码生成另一个33来利用。尽快如此,大多数种类的对象都会在不再引用时马上回收。对于那些不会被回收的,缓冲机制与代码并没有什么关系。
判断是否相等
>>> L=[1,2,3]
>>> M=L
>>> L==M
True
>>> L is M
True
==检查对象是否有相同的值。 is操作符,检查对象的同一性。如果两个变量名精准地指向同一个对象,它会返回True。所以这是一种更严格的相等测试。
实际上,is只是比较现实引用的指针。所以如果必要的话是代码中检测共享引用的一种方法。如果变量名引用值相等。但是为不同的对象,它的返回值将是False.
>>> L=[1,2,3]
>>> M=[1,2,3]
>>> L==M
True
>>> L is M
False
>>> id(L)
676367788
>>> id(M)
676367724
通过id()函数可以看到两个变量指向不同的对象。
>>> X=33
>>> Y=33
>>> X==Y
True
>>> X is Y
True
>>> id(X)
674748936
>>> id(Y)
674748936
这个is测试返回True因为小的整数和字符串被缓存被复用了。
如果想更进一步了解,可以向Python查询一个对象应用的次数:在sys模块中的getrefcount函数返回对象应用的次数。
>>> import sys
>>> sys.getrefcount(33)
13
>>> sys.getrefcount(1)
427
>>> sys.getrefcount(00)
296
>>> sys.getrefcount(99)
6