本文所用的可嵌套对象和不可嵌套对象,官方称之为可变对象和不可变对象。我只是觉得前者更便于理解。
首先有两个基本知识:
Python 中的所有变量皆为对象
Python 函数参数传递的是对象的引用(跟C 语言的指针差不多)
不可嵌套对象赋值常量 不可嵌套对象赋于之前不同的值时,会重新分配内存,并将引用指向新的内存。下例将2
这个整型对象的引用赋值给a
1 2 3 4 5 6 7 a = 1 print (a, id (a))a = 2 print (a, id (a))1 140709099219384 2 140709099219416
不可嵌套对象赋值不可嵌套对象 1 2 3 4 5 6 7 8 9 10 11 12 a = 1 b = a print (a, b)print (id (a), id (b))b = 2 print (a, b)print (id (a), id (b))1 1 140709083031992 140709083031992 1 2 140709083031992 140709083032024
不可嵌套对象赋值可嵌套对象 1 2 3 4 5 6 7 a = 1 print (a, id (a))a = [1 , 2 , 3 ] print (a, id (a))1 140709083031992 [1 , 2 , 3 ] 1588587600064
这里a
变成了整个列表对象的引用
不可嵌套对象作为函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def test (param1 ): print (param1, id (param1)) param1 = 2 print (param1, id (param1)) return param1 a = 1 print (a, id (a))a = test(a) print (a, id (a))1 140711413885368 1 140711413885368 2 140711413885400 2 140711413885400
可以看到a 作为函数参数或是返回值时,传递的始终时a 的引用。由于a 为不可嵌套对象,所以在函数内部赋值的时候引用发生了改变。
不可嵌套对象作为全局变量时被函数内部访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 a = 1 def test (): print (a, id (a)) a = 2 print (a, id (a)) if __name__ == '__main__' : test() print (a, id (a)) Traceback (most recent call last): File "c:\Users\Xe-131\Desktop\test.py" , line 45 , in <module> test() File "c:\Users\Xe-131\Desktop\test.py" , line 40 , in test print (a, id (a)) ^ UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 a = 1 def test (): print (a, id (a)) print (a.__class__) print (a.__add__(2 )) print (a, id (a)) if __name__ == '__main__' : test() print (a, id (a)) 1 140710631975352 <class 'int' > 3 1 140710631975352 1 140710631975352
可以看到在函数内部对a
进行赋值会报错,但使用a
内部的属性或者方法时却没问题。如果函数内部有赋值操作,python
会将a
理解为局部变量,从而第一句print(a, id(a))
就会报错,因为在这句话之前a
并没有初始化。
如果需要对a 进行赋值,有两种对待方式:
当成全局变量:在函数内部用关键词声明
这里全程都是用的时全局变量a
,函数执行结束后,全局变量a
的值确实改变了1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 a = 1 def test (): global a print (a, id (a)) a = 2 print (a, id (a)) if __name__ == '__main__' : print (a, id (a)) test() print (a, id (a)) 1 140711413885368 1 140711413885368 2 140711413885400 2 140711413885400
当成局部变量:在用到之前先初始化声明一下
函数内部的a
跟外部的a
是两个东西1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 a = 1 def test (): a = 2 print (a, id (a)) if __name__ == '__main__' : print (a, id (a)) test() print (a, id (a)) 1 140710631975352 2 140710631975384 1 140710631975352
可嵌套对象赋值常量(不可嵌套对象) 1 2 3 4 5 6 7 8 a = [1 , 2 , 3 ] print (a, id (a))a = 1 print (a, id (a))[1 , 2 , 3 ] 2023166382400 1 140710631975352
可嵌套对象元素赋值常量(不可嵌套对象) 1 2 3 4 5 6 7 8 a = [1 , 2 , 3 ] print (a, id (a), a[0 ], id (a[0 ]))a[0 ] = 4 print (a, id (a), a[0 ], id (a[0 ]))[1 , 2 , 3 ] 1281264521536 1 140710631975352 [4 , 2 , 3 ] 1281264521536 4 140710631975448
对a
的元素赋值并不会改变a
本身的指向,而是改变其元素的指向
可嵌套对象赋值可嵌套对象 1 2 3 4 5 6 7 8 9 a = [1 , 2 , 3 ] b = a print (a, id (a), b, id (b))b[0 ] = 4 print (a, id (a), b, id (b))[1 , 2 , 3 ] 2346644148544 [1 , 2 , 3 ] 2346644148544 [4 , 2 , 3 ] 2346644148544 [4 , 2 , 3 ] 2346644148544
这里我们改变b
的内部元素,就连a
内部元素也一起改变了。是因为a b
都指向一个列表,b[0] = 4
操作是将列表中的引用改变,无论改变与否它都属于这个列表。
想要获得两个互不相干的列表,可以去学习深拷贝和浅拷贝的内容。
可嵌套对象作为函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def test (param ): print (param, id (param)) param[0 ] = 4 print (param, id (param)) a = [1 , 2 , 3 ] print (a, id (a))test(a) print (a, id (a))[1 , 2 , 3 ] 2395825574208 [1 , 2 , 3 ] 2395825574208 [4 , 2 , 3 ] 2395825574208 [4 , 2 , 3 ] 2395825574208
函数传递的是列表的引用,所以列表内部元素的改变会被传递到函数外部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def test (param ): print (param, id (param)) param = [4 , 5 , 6 ] print (param, id (param)) a = [1 , 2 , 3 ] print (a, id (a))test(a) print (a, id (a))[1 , 2 , 3 ] 2408679280960 [1 , 2 , 3 ] 2408679280960 [4 , 5 , 6 ] 2408681482880 [1 , 2 , 3 ] 2408679280960
这里对传入的引用改变方向,指向了另一个列表
通过实验,我们可以知道,将某个对象传入函数时,实际上是创建了一个新的指向原对象的引用
可嵌套对象作为全局变量被函数访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def test (): print (a, id (a)) a = [4 , 5 , 6 ] print (a, id (a)) a = [1 , 2 ,3 ] if __name__ == '__main__' : print (a, id (a)) test() print (a, id (a)) [1 , 2 , 3 ] 1967735312704 Traceback (most recent call last): File "c:\Users\Xe-131\Desktop\test.py" , line 122 , in <module> test() File "c:\Users\Xe-131\Desktop\test.py" , line 111 , in test print (a, id (a)) ^ UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def test (): print (a, id (a)) a[0 ] = 4 print (a, id (a)) a = [1 , 2 ,3 ] if __name__ == '__main__' : print (a, id (a)) test() print (a, id (a)) [1 , 2 , 3 ] 1741292507456 [1 , 2 , 3 ] 1741292507456 [4 , 2 , 3 ] 1741292507456 [4 , 2 , 3 ] 1741292507456
全局变量的作用就是,这个变量的引用时全局可见的。因为并不是通过参数传递给函数的,所以函数内部使用的引用就是原来的引用。
原理与不可嵌套函数作为全局变量被函数访问一样,当进行a = [1, 2 ,3]时,python 就会混淆,它不知道你是想新建一个与a 同名的局部变量还是将全局变量a 的指向改变。解决方法与不可嵌套变量一样。
但是如果只改变其内部属性或者元素的话,并不会引起混淆,所以不会报错。