核心一句话:
不是 finally 特殊,是 Python 没有块级作用域。if/try/for/while 的代码块,都不会隔离变量。
python
try:
f = open("a.txt")
finally:
print(f) # 能拿到
f 属于外层函数/模块作用域 ,try 只是普通语句块,不新建作用域。
对比 Java:
Java 的 {} 是块作用域,在 try { 里面定义变量 },出了这个大括号直接消失,finally 拿不到。
⚠️ 但是有一个巨大坑:
如果 f = open(...) 这一行抛出异常 ,赋值没完成,名字 f 根本没创建,finally 访问直接 NameError!
python
try:
f = open("不存在的文件.txt") # 这里报错,f 没绑定成功
finally:
print(f) # NameError: name 'f' is not defined
对比两种写法感受差别
python
# 写法1:不安全(你写的)
try:
f = open("test.txt", "w")
try:
f.write("hi")
finally:
f.close()
finally:
print(f.closed)
python
# 写法2:安全,先初始化
f = None
try:
f = open("test.txt", "w")
f.write("hi")
finally:
if f is not None:
f.close()
print(f.closed)
总结
- Python:作用域只有函数、类、模块;代码块
:后面缩进块不隔离变量 - 能访问 ≠ 一定存在!赋值语句抛异常时变量不存在
- 这就是
with上下文管理器设计出来解决的痛点,自动处理,不用手动兜底判断
简单记:Python 看缩进,不看大括号块;Java 看大括号块作用域。
一句话
Python 用缩进(空格/Tab) 代替别的语言(Java/C)的 {} 大括号,用来划分代码块 ;
但是!缩进只管代码属于哪一块逻辑,不新建作用域!
这是两个独立概念,很多人容易混在一起。
1. 缩进 = 划分代码块(控制归属)
Java:
java
if (true) {
int a = 10; // 在{}块里面
}
// 出了{},块结束
Python:
python
if True:
a = 10 # 缩进,属于if内部代码块
print(a) # ✅ 可以访问!
冒号 : + 缩进,代表开启一块代码。
缩进对齐的代码属于同一组。
python
if True:
print("第一行")
print("第二行") # 和上面缩进一样,同属if
print("外面") # 缩进退回,不属于if
重点区分两件事:
- 缩进:控制代码什么时候执行(代码分组)
- 作用域:控制变量名字在哪里能被读到
Python:if/try/for/while 的缩进块,只分组代码,不开辟新作用域
只有 def、class、模块 才会产生新作用域。
2. 举 try 的例子帮你串起来
python
try:
f = open("test.txt")
f.write("123")
finally:
print(f.closed)
try:后面缩进的代码,是 try 的执行块;finally:后面缩进的,是 finally 的执行块;- 但是
f = ...是在函数/模块作用域创建的名字,只是写在 try 的缩进块里; - 所以 finally 代码块(同一层作用域)看得见这个变量。
如果是 def,就不一样了,def 会隔离:
python
def test():
b = 20
test()
print(b) # ❌ NameError,def新建作用域,外面看不到
3. 容易踩的误区纠正
❌ 错误理解:
"Python缩进块就等于Java的大括号,里面变量外面不能访问"
✅ 正确:
Python缩进只是用来标记代码归属;
Java的
{}同时做两件事:划分代码块 + 创建块级作用域。
极简对比表
| 语言 | 代码块分隔 | 是否有块级作用域 |
|---|---|---|
| Java / C++ | {} |
✅ 有,块内变量出块失效 |
| Python | 缩进+冒号 | ❌ 没有(if/try/for都没有) |
小测试
python
for i in range(3):
num = i * 2
print(num) # 输出4,可以拿到!
Java 等价写法,循环里面定义的变量循环外面直接访问不到。