Python嵌套函数修改外层布尔变量报错,用列表却正常?
下面是一段判断二叉树是否平衡的Python代码,它用列表类型balanced标记树是否平衡,运行正常:
class Solution: def isBalanced(self, root: Optional[TreeNode]) -> bool: balanced = [True] def node_height(root): if not root or not balanced[0]: return 0 left_height = node_height(root.left) right_height = node_height(root.right) if abs(left_height - right_height) > 1: balanced[0] = False return 0 return 1 + max(left_height, right_height) node_height(root) return balanced[0]
但如果把第3行改成balanced = True,并将第6、13、19行的balanced[0]替换为balanced,程序就会报错。
我测试了另一段代码,发现嵌套函数能正常读取外层的布尔变量c:
class solution: def func(): a = 5 b = 7 c = True def nested_func(): return (a + b, c) return nested_func() print('sum is:', func())
这段代码输出sum is: (12, True),说明嵌套函数可以访问外层变量。那为什么前一种场景修改布尔变量会报错,用列表就没问题?
这其实是Python的变量作用域规则和可变/不可变类型差异共同导致的:
读取 vs 修改的作用域规则
嵌套函数可以直接读取外层作用域的变量(比如你测试代码里的c),但如果要修改外层变量,Python默认会把这个变量当成嵌套函数的局部变量——除非你用nonlocal(针对嵌套外层的变量)或global(针对全局变量)声明它的来源。
当你把balanced改成布尔值后,在node_height里执行balanced = False时,Python会认为你是在定义一个局部变量balanced,但这个变量在赋值前就被if not balanced引用了,所以会抛出UnboundLocalError(局部变量在赋值前被引用)。可变类型 vs 不可变类型的区别
布尔值、整数、字符串都是不可变类型:你没法直接修改它们的内部值,只能重新赋值(相当于创建一个新对象)。
列表是可变类型:你可以修改列表内部的元素(比如balanced[0] = False),这并不会重新给balanced变量赋值,只是修改了它指向的列表对象的内容。所以Python不会把balanced当成嵌套函数的局部变量,而是继续引用外层作用域的那个列表。不用列表的解决方法
如果不想用列表,你可以用nonlocal声明告诉Python:这个变量不是局部的,来自外层作用域。修改后的代码如下:
class Solution: def isBalanced(self, root: Optional[TreeNode]) -> bool: balanced = True def node_height(root): nonlocal balanced if not root or not balanced: return 0 left_height = node_height(root.left) right_height = node_height(root.right) if abs(left_height - right_height) > 1: balanced = False return 0 return 1 + max(left_height, right_height) node_height(root) return balanced
这样修改后,布尔变量的版本也能正常运行。
内容的提问来源于stack exchange,提问作者DHK

