在实现名片管理系统中修改名片的功能时,可以使用 for 循环遍历列表,利用列表方法或赋值修改名片列表,示例如下:
card_list = [{"key0": 0, "key1": 1}, {"key0": 2, "key1": 3}] print("初始列表值为:%s"
% card_list) for list_tmp in card_list: list_tmp["key0"] = 4
print("for循环遍历后值为:%s" % card_list)
执行结果:
初始列表值为:[{'key0': 0, 'key1': 1}, {'key0': 2, 'key1': 3}]
for循环遍历后值为:[{'key0': 4, 'key1': 1}, {'key0': 4, 'key1': 3}]
疑问:修改临时列表 list_tmp 的值,变量 card_list 的值为何也会随之变化?
以下是个人理解:
1. Python 中变量的存储方式
数值类型变量 以 int 为例,新创建一个变量时,形象的来说,Python
会先为右侧的数值分配一个储存空间,再将左侧的变量名以类似标签的方式贴在上面。
a = 1 b = 1 print("a的id:%d " % id(a), " b的id:%d" % id(b))
执行结果:
a的id:9330912 b的id:9330912
可看出,数值 1 被分配的空间 id=9330912,第一行语句将 a 的标签贴在了 1 的空间上,当执行第二行时,Python 只需要将 b 的标签也贴在
id=9330912 上即可,所以 a 与 b 引用的是同一个地址;字符串变量 也是同理。
(需要注意,Python 对于一定范围的数值类型会提前创建空间,也就是说他们的 id
都是固定的,当数据被引用时只需要将标签贴到上面。而超出范围的将不再遵循上述规则,即使变量赋的值相同,他们所引用的地址也不再相同,Linux 终端中
python / ipython 3.6.5 中 int 类型固定的范围是 [-5, 256],但是在 Pycharm 中使用 3.6.5
版本解释器,它的范围却大的多,而且我也没找到范围和规律...)
针对 列表、元祖、字典
这些储存着多个值的变量来说,我理解为嵌套储存,即并不是把所有数据直接储存在一个空间里,而是先为变量中的最小单位的数据(数值、字符串)分配空间,父阶的
index 、key 将以类似指针的方式指向数据,以列表来说,执行下列代码:
gl_list = ["name", 123] print("列表的id:%s" % id(gl_list)) print("index0的id:%s" %
id(gl_list[0])) print("index1的id:%s" % id(gl_list[1])) c = "name" d = 123
print("c的id:%d " % id(c), " d的id:%d" % id(d))
执行结果:
列表的id:139865592449800
index0的id:139865716779920
index1的id:9334816
c的id:139865716779920 d的id:9334816
结果显示列表的 id 和 index 的 id 不同,这里假设下,列表的地址中存储的是每个 index 的 id ,当列表变量被引用时,再分别从对应的
index 的 id 中获取数据。
在上面的代码的末尾,我又给 c 和 d 赋予了与列表中元素相同的值,从结果来看,他们的 id
都对应相同,这也说明,列表存储时会分别为每一个子数据单独分配空间,所以当列表被创建时,先为name 和 123 创建存储空间,再将 index[0] 和
index[1]的标签分别贴到对应的空间上,当为变量 c 和 d 赋值时,再将他们的标签也贴到相同的空间上。
元组同理。而针对字典变量,键值对中的 key 可以理解为标签,value 可以理解为需要分配空间的数据。
2. for 循环遍历
先执行示例代码:
list_test = [1, 2, 3] print("index0的id:%d" % id(list_test[0]), "index1的id:%d"
% id(list_test[1]), "index2的id:%d" % id(list_test[2])) i = 1 for tmp in
list_test: print("tmp%d的id:%d" %(i, id(tmp)), end=" ") i += 1
执行结果:
index0的id:9330912 index1的id:9330944 index2的id:9330976
tmp1的id:9330912 tmp2的id:9330944 tmp3的id:9330976
依我的理解,for...in... 循环遍历原理即 利用 .iter() 方法为可迭代对象创建一个迭代器,再利用 .next()
方法将迭代器中所有的元素依次赋值给一个临时变量,在上面的代码中, tmp 就可以看作这个临时变量,那么在第一次循环中,简化后相当于for
循环执行了以下赋值操作:
tmp = list_test[0]
关于对象的复制,有三种类型:赋值、浅拷贝、深拷贝。这里使用的是赋值,等同于将 tmp 的标签贴在了保存数据 0 的空间上。
顺便一提,如果此时在 for 循环内部将 tmp 重新赋值,相当于将 tmp 的标签撕下,贴到另一个地址上,因此不会影响 list_test[0] 的引用。
3. 列表与字典嵌套类型变量的 for 循环遍历详解
最后来看下最开始的疑问,对于列表与字典嵌套类型的变量,进入 for 循环后,是怎么通过临时变量来修改全局变量的呢?
定义一个列表与字典嵌套的变量 gl_list ,执行以下代码:
gl_list = [{"key0": 0}, {"key0": 1}] print("打印gl_list:\n%s" % gl_list)
print("列表index0的id:%d" % id(gl_list[0]), "列表index0[\"key0\"]的id:%d" %
id(gl_list[0]["key0"])) print("列表index1的id:%d" % id(gl_list[1]),
"列表index1[\"key0\"]的id:%d" % id(gl_list[1]["key0"])) i = 1 for tmp in gl_list:
print("-" *30) print("第%d次循环,将index%d的值赋予tmp" % (i, i-1)) print("修改前tmp的id:%d"
% id(tmp)) print("修改前tmp[\"key0\"]的id为:%d" % id(tmp["key0"]))
print("修改前tmp的值为:%s" % tmp) print("将 key0 的值改为 888") tmp["key0"] = 888
print("修改后tmp的id:%d" % id(tmp)) print("修改后tmp[\"key0\"]的id为:%d" %
id(tmp["key0"])) print("修改后tmp的值为:%s" % tmp) i += 1 print("\n遍历后列表的值为:%s" %
gl_list) print("列表index0的id:%d" % id(gl_list[0]), "列表index0[\"key0\"]的id:%d" %
id(gl_list[0]["key0"])) print("列表index1的id:%d" % id(gl_list[1]),
"列表index1[\"key0\"]的id:%d" % id(gl_list[1]["key0"]))
可直接看执行结果:
打印gl_list:
[{'key0': 0}, {'key0': 1}]
列表index0的id:140171287909864 列表index0["key0"]的id:9330880
列表index1的id:140171287909936 列表index1["key0"]的id:9330912
------------------------------
第1次循环,将index0的值赋予tmp
修改前tmp的id:140171287909864
修改前tmp["key0"]的id为:9330880
修改前tmp的值为:{'key0': 0}
将 key0 的值改为 888
修改后tmp的id:140171287909864
修改后tmp["key0"]的id为:140171166844048
修改后tmp的值为:{'key0': 888}
------------------------------
第2次循环,将index1的值赋予tmp
修改前tmp的id:140171287909936
修改前tmp["key0"]的id为:9330912
修改前tmp的值为:{'key0': 1}
将 key0 的值改为 888
修改后tmp的id:140171287909936
修改后tmp["key0"]的id为:140171166844048
修改后tmp的值为:{'key0': 888}
遍历后列表的值为:[{'key0': 888}, {'key0': 888}]
列表index0的id:140171287909864 列表index0["key0"]的id:140171166844048
列表index1的id:140171287909936 列表index1["key0"]的id:140171166844048
对于该段代码可以搭配下图理解,首先定义 gl_list 列表,其由两个字典组成,每个字典只有定义了一个键值对,那么 Python
创建这个列表的步骤应该如下:
(1) 先为 0 和 1 创建储存空间 ; 将 key0 的标签分别贴到对应数值的空间上
(2) 创建保存 key 的空间 ;将 index 标签贴到 key 的空间上
(3) 创建保存 index 的空间 ;将列表的标签贴到 index 的空间上
接下来进入 for 循环,以第一次循环为例:
(1) 将 gl_list[0] 值赋给 tmp ,tmp = gl_list[0] ,将 tmp 的标签与 index[0] 标签贴在一起
(2) 执行 tmp["key0"] = 888,将 ["key0"] 的标签撕下,贴到数值 888 对应的地址上
进入 for 循环还未为元素重新赋值前:
for 循环遍历后:
可以清晰的看出,index[0] 与 tmp 引用的都是 ["key0"] 这个地址,地址变了,那么引用的值自然也会相应改变。