前段时间 对实际项目重构代码,然后在我给变量重新命名的时候,
for 循环中循环变量名 和外层 的变量名,取名相同,结果直接覆盖了外层变量的值, 由此 我就查了一下 文档有了这篇文章。
编写 hello.py
for num in [1, 5, 10]:
print(num,end=' ')
print(f"=== after for:{num}")
结果输出
1 5 10 === after for:10
num 这个变量 在 for 循环代码块 结束后,仍然有效。
也就是说在 for 循环 迭代 一个可迭代对象的时候,离开for 循环的代码段,这个 循环变量num 依然有效 。
如果迭代是 对象没有值的话,num 就不会赋值 这个时候,for 循环 就不能进入,此时num 也没有赋值,即没有num 这个变量.
for num in []:
print(num,end=' ')
print(f"=== after for:{num}")
结果如下:
如果我在 模块级别定义一个num 的变量,然后 for 循环的索引变量 也是num 并且 迭代对象 不为空的时候, 这个时候 就有可能出现 问题,
因为此时 for 循环的 索引变量 num 会覆盖外层的变量, 使外层变量的值发生改变。
num =100
for num in [1,2,3]:
print(num,end=' ')
print(f"=== after for:{num}")
结果如下:
1 2 3 === after for:3
从结果可以看出 num 的值是3 ,说明 经过 for 循环后 ,num 最后赋值为3, 和 上面 num =100 有冲突,直接 把 num 的值 覆盖了。
这是因为 外层num 和 for 循环里面的 num 是处于一个作用域, 都是模块级别的变量。 所以都在模块级别的变量,不能重复。
那么如何避免这种问题呢?
方法一:
修改 循环变量的名称,不要和外层变量名一样,这样两个变量名称,就不会冲突了。
方法二 :
可以把for 循环里面的内容 封装成一个函数,这样就可以 避免变量名 覆盖的问题。
# -*- coding: utf-8 -*-
num = 100
def print_num():
for num in [1, 2, 3]:
print(num, end=' ')
print()
if __name__ == '__main__':
print_num()
print(f"=== after print_num:{num}")
pass
从结果可以看出, num 没有被污染, 这两个 num 在不同的作用域里面。
这样python 解释器 就不会覆盖。
正好来一起来探讨 变量作用域的问题 ,
作用域 就是指 一个变量的作用范围在哪里。 一个变量的作用范围 或者说 在哪一代码段 是有效的。
在python中
全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
全局名称 就是 一般定义在模块级别的变量。
局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
局部 是指 定义在函数 或者类中的一些变量。
# -*- coding: utf-8 -*-
# 全局变量
num = 100
print(f"module num:{num}")
def visit_num():
print(f"visit num:{num}")
if __name__ == '__main__':
visit_num()
print(f"main num:{num}")
pass
由于num 定义在 模块级别 是一个全局变量,此时 num 之后的所有代码 都可以访问到这个变量的。 这就是全局变量
你可能 直接感觉 visit_num 函数里面修改这个变量不就可以了吗,
如下面的代码段
# -*- coding: utf-8 -*-
num = 10000000
print(f"module num:{num}")
def visit_num():
print(f"visit num:{num}")
num = 0
if __name__ == '__main__':
visit_num()
print(f"main num:{num}")
运行代码发现会报错
原因是 解释器无法知道 num=0 是想在 visit_num() 这个函数定义一个变量叫num,还是想修改 上面定义的全局变量,所以 就报错了。
那么如何让 解释理解 num 是要修改 全局变量呢?
# -*- coding: utf-8 -*-
num = 10000000
print(f"module num:{num}")
def visit_num():
# 这里使用关键字进行声明
global num
print(f"visit num:{num}")
# modify num
num = 0
if __name__ == '__main__':
visit_num()
print(f"main num:{num}")
这样就可以正常 的修改 num 的值了。 就是需要在 函数前面的位置 用 global
关键字 进行声明 num, 这样解释器 就能明白我现在要 更改的是一个全局变量里面的num.
思考一下:
如果想没有添加 global 声明 ,然后直接修改num 的值 ,会有什么结果呢?
# -*- coding: utf-8 -*-
num = 10000000
print(f"module num:{num}")
def visit_num():
# modify num
num = 0
print("modify num done.")
if __name__ == '__main__':
visit_num()
print(f"main num:{num}")
pass
可以看出 这里并没有把num 的值 修改,原因是 在visit_num 中定义了一个新的 num 变量,这个变量 和外层的变量 不在一个scope 里面, 一个是局部变量,一个是全局变量。 所以 python解释器 就可以知道这是新定义的一个变量。 和外层的模块级别的 num 变量没有关系。
所以 简单总结一下, 如果在函数中定义了一个和模块级别的变量的相同的名称,这两个变量 是 不相互影响的。
但是如果都在模块级别定义相同的变量名称,就会出现变量名 被覆盖的情况。 不论这个变量出现在 for 循环,还是 其他的任何位置,只要 这段代码被执行了,就会覆盖之前的变量。
num = 1000
for num in [1, 2]:
pass
但是有一种比较特殊的情况,对于列表推导式的情况下,它的循环变量只在 自己的列表推导式里面, 出了列表推导式,变量就不存在了。
>>> l = [num ** 2 for num in range(5)]
>>> l
[0, 1, 4, 9, 16]
>>> num
Traceback (most recent call last):
File "", line 1, in <module>
NameError: name 'num' is not defined
对于推导式的作用域就类似 在函数里面了, 出了列表推导式,变量 就失效了。
平常还是要多留意不同语言之间的差别,每一种语言的设计,都有每一种语言的设计理念,只有理解的作者的理念,才能体会到语言之美,不断总结吧,也给自己一个小小的教训,不要想当然的认为 ,这个应该是这样的,多去实践,来去检查自己的想法。