[Python 学习记录] 变量的储存与 for 循环遍历修改变量的原理

        在实现名片管理系统中修改名片的功能时,可以使用 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 都对应相同,这也说明,列表存储时会分别为每一个子数据单独分配空间,所以当列表被创建时,先为name123 创建存储空间,再将 index[0] index[1] 的标签分别贴到对应的空间上,当为变量 cd 赋值时,再将他们的标签也贴到相同的空间上。

元组同理。而针对字典变量,键值对中的 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"] 这个地址,地址变了,那么引用的值自然也会相应改变。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值