洗礼灵魂,修炼python(23)--自定义函数(4)—闭包进阶问题—>报错UnboundLocalError: local variable 'x' referenced before assign

本文详细解析了Python中的闭包概念及其工作原理,包括如何正确处理内部函数对外部变量的引用,以及如何避免常见的UnboundLocalError错误。

闭包(lexical closure)

什么是闭包前面已经说过了,但是由于遗留问题,所以单独作为一个章节详解讲解下

不多说,看例子:

def funx(x):
    def funy(y):
        return x*y     #此时的funy函数对外层funx函数的变量调用,则称为闭包
    return funy

 

结果:

>>> i=funx(4)
>>> i
<function funx.<locals>.funy at 0x000000000331B7B8>
>>> i(5)
20
>>> a=funx(4)(5)   #切不可写成a=funx(4(5)),为什么自己去研究
>>> a
20

 

再一例:

 

 

但是呢,有个很严重的问题,并不是外层的变量都可以调用的,如果是这样写的话:

def fun1():
    x=5
    def fun2():
        x*=x   
        return x
    return fun2

 结果:

 

问题来了:UnboundLocalError: local variable 'x' referenced before assignment

这是为嘛啊?看报错信息,提示的是本地变量x再调用前没有被定义,也就是说,fun2函数用的x并不是我们以为的fun1的变量,因为这里写法和上面的例子是不同的,那我改成一样的写法看看:

 

还是不行,为嘛啊?

我个人的理解是:在之前使用闭包是没有对传入的值进行改动的,是直接返回出来的,当我们传入4或者5时,传入这个举动就已经赋值定义好了X和y,这都是前一章参数的功劳。而后面的写法都是对传入的值有改变(x*=x),因为x指向的是一个不可变对象—常量,我们对传入的值已经改变,也就是内存ID发生改变,那么这个被改过的x*=3得到的X则是一个新的变量,在fun2函数空间内来说,这个X是没有被定义的,所以报错。而在之前我们只是声明了一个原始X的变量,所以返回时不报错

官方解释是:由于python对变量的搜索机制引起,当在调用函数时,如果有一个函数在内部对外层函数的变量做赋值操作 ,该变量会被认为是本地的(这就是前面说的屏蔽),所以如果你修改变量的值就会把其变成局部变量,在修改的同时就会创建一个同名的局部变量来屏蔽,那么该变量的引用自然就会出现未定义,所以这就解释为何直接调用不报错,而赋值操作就会报错

图解(个人理解):

 

 

那么这种情况怎么解决呢?在以后的开发中,总有要重新赋值的操作吧?是的,解决方法是有的,而且是多个方法:

方法一:使用nonlocal关键词

 

 

那么再直接以原写法加nonlocal关键词看看:

 

果然可行

注意:nonlocal参数在python3中才有,python2里没有

方法二:使用global关键词

使用global参数有两种情况:

1.已经有一个全局变量被定义,并且希望调用全局变量

2.函数内外层都把对象定义为全局变量,并且希望调用外层函数的变量

方法三:很“猥琐”的一招:建一个新的本地变量不要改动上层函数的变量

是的,很猥琐,基本和外层函数没关系了,只不过值是一样的而已。顺便一说,虽然猥琐,但是确实是个办法,这样的操作在类定义里也会用到

方法四:使用容器类型

容器类型:如果某个对象包含对其他对象的引用,则将其称为容器。

容器不是存放在栈(后进先出的则为栈)里面,不受屏蔽影响,变量不会被屏蔽掉。字符串,列表,元祖都是容器类型

 好,那么我们试一下看看:

代码:

def fun1():
    x=[5,6]
    def fun2():
        x[0]*=x[0]
        return x[0]
    return fun2

结果是可行:

 

 

那么这个方法就会带来一个新的问题,先看看例子再说:

 

x并没有变对不对,这符合在前面说的,字符串,整形(常量)都是不可变对象,所以需要改则得重新赋值,这里要说明一下,变量本身是不可变对象,因为变量的赋值只是贴的一个标签而已,在前面的章节里说过的

那么使用容器类型呢?

 

 

果然是变了,因为list本身就是一个可变对象。但是说个很严重的问题,假如你在实际开发中,你的数据是list,这个数据很重要,你这样调用一次,list里的数据就变了,那么如果操作错或者说如果在测试中做测试,那岂不用一次少一次?这个时候,注意,你可以使用列表切片或者拷贝的方法来避免这种事发生

 

最后做个总结

  • 内部函数,不修改全局变量可以访问全局变量
  • 内部函数,修改同名全局变量,则python会认为它是一个局部变量
  • 在内部函数修改同名全局变量并调用变量,则引发UnboundLocalError: local variable 'x' referenced before assignment

 

看到这里,再结合前面的是不是有点懵X了?哈哈,没关系,多练习,多理解,很简单,你会看懂的

<think>我们遇到了一个Python错误:`UnboundLocalError: local variable 'op_points' referenced before assignment`。这个错误通常发生在函数内部,当我们尝试使用一个局部变量(在函数内部定义的变量)之前,没有对它进行赋值(即初始化)操作。 根据引用[2][3][4][5]的解释,这个错误的根本原因是:在函数内部,如果对一个变量进行赋值操作(例如使用`=`赋值,或者使用`+=`等复合赋值),Python会将该变量视为局部变量。但是,如果在赋值之前就尝试读取(引用)这个变量,就会引发`UnboundLocalError`。 ### 错误示例分析 假设我们有以下代码: ```python x = 10 # 全局变量 def my_func(): print(x) # 这里会报错,因为下一行对x赋值,使得x成为局部变量,但此时还未赋值 x = 20 my_func() ``` 运行上述代码会抛出: ``` UnboundLocalError: local variable 'x' referenced before assignment ``` ### 解决方案 根据引用[2][3][4][5],我们可以采用以下方法解决: #### 方法1:使用`global`关键字声明变量为全局变量 如果确实需要修改全局变量,在函数内部使用`global`声明该变量。 ```python x = 10 def my_func(): global x # 声明x是全局变量 print(x) # 现在可以读取全局变量x x = 20 # 修改全局变量x my_func() print(x) # 输出20 ``` #### 方法2:使用函数参数传递 如果可能,将变量作为参数传递给函数,避免使用全局变量。 ```python x = 10 def my_func(x_val): print(x_val) x_val = 20 return x_val x = my_func(x) print(x) # 输出20 ``` #### 方法3:在函数内部避免在赋值前引用 确保在函数内部,对一个局部变量赋值之前,不要读取它。可以先赋值,再使用。 ### 针对用户的具体错误 用户错误信息是:`local variable 'op_points' referenced before assignment`。这意味着在函数中,存在对变量`op_points`的引用(读取)操作,但在此之前没有对`op_points`进行赋值,而且在该函数中后面有对`op_points`的赋值操作(导致Python认为`op_points`是局部变量)。 #### 解决步骤 1. **定位错误**:找到报错的行,查看该行之前是否有对`op_points`的赋值(初始化)。 2. **判断变量用途**: - 如果`op_points`是全局变量,并且需要在函数内部修改它,则在函数开头使用`global op_points`声明。 - 如果`op_points`是外层函数中的变量(闭包),则使用`nonlocal op_points`声明。 - 如果`op_points`只是函数内部使用的变量,那么确保在引用之前已经对它进行了初始化(赋值)。 #### 示例修复 假设原代码为: ```python op_points = [] # 全局变量 def process_data(): # 这里尝试使用op_points for point in op_points: # 报错行:UnboundLocalError print(point) op_points = [1,2,3] # 这里对op_points赋值,导致Python认为它是局部变量 process_data() ``` 修复方法1:使用`global` ```python op_points = [] # 全局变量 def process_data(): global op_points # 声明使用全局变量 for point in op_points: # 现在可以正常使用 print(point) op_points = [1,2,3] # 修改全局变量 process_data() ``` 修复方法2:避免使用全局变量(推荐) ```python def process_data(op_points): # 通过参数传递 for point in op_points: print(point) op_points = [1,2,3] # 注意:这里修改的是局部变量op_points,不会影响外部 return op_points global_op_points = [] global_op_points = process_data(global_op_points) # 如果需要修改外部变量,可以返回并重新赋值 ``` ### 注意事项 - 尽量避免使用全局变量,因为全局变量会使得代码难以维护和调试。 - 如果函数内部需要修改一个可变对象(如列表、字典),可以将其作为参数传入,并在函数内部直接修改(注意:这会影响外部对象)。如果不想影响外部,可以创建副本。 ### 相关问题 1. Python中全局变量和局部变量的作用域规则是什么? 2. 如何避免在Python中使用全局变量? 3. Python闭包中如何修改外部函数的变量? 4. Python的`nonlocal`关键字有什么作用? 5. 如何调试Python中的`UnboundLocalError`错误? [^1]: Python Error - UnboundLocalError: local variable xxx referenced before assignment [^2]: 深度分析python UnboundLocalError: local variable ‘x‘ referenced before assignment [^3]: python UnboundLocalError: local variable 'x' referenced before assignment [^4]: PythonUnboundLocalErrorlocal variable 'xxx' referenced before assignment [^5]: Python问题UnboundLocalErrorlocal variable 'xxx' referenced before assignment</think>### 解决 Python 中的 `UnboundLocalError: local variable 'op_points' referenced before assignment` 错误 这个错误发生在 Python 函数中尝试读取一个局部变量时,该变量尚未被赋值。根本原因是 Python 的变量作用域规则(LEGB规则)[^3]。当函数内部存在对变量的赋值操作时,Python 会将其视为局部变量,即使外部存在同名变量[^4][^5]。 #### 错误原因分析 ```python op_points = [] # 全局变量 def process_data(): # 尝试读取 op_points print(len(op_points)) # ❌ 错误发生位置 # 赋值操作使 Python 认为 op_points 是局部变量 op_points = load_data() # 赋值语句 ``` 在以上代码中: 1. 函数内存在 `op_points = ...` 赋值操作 2. Python 因此将 `op_points` 视为局部变量 3. 但在赋值前尝试读取 `len(op_points)` 导致错误[^3] #### 解决方案 ##### 方法 1:使用 `global` 声明(推荐用于全局变量) ```python op_points = [] # 全局变量 def process_data(): global op_points # 声明使用全局变量 print(len(op_points)) # ✅ 现在可以正确读取 op_points = load_data() # 修改全局变量 ``` ##### 方法 2:使用函数参数传递 ```python def process_data(points): # 通过参数接收 print(len(points)) # ✅ 直接使用参数 points = load_data() # 修改局部副本 return points # 返回修改后的值 # 调用方式 op_points = process_data(op_points) # 传入并接收返回值 ``` ##### 方法 3:使用 `nonlocal`(用于嵌套函数) ```python def outer_function(): op_points = [] # 闭包变量 def inner_function(): nonlocal op_points # 声明使用闭包变量 print(len(op_points)) op_points = load_data() inner_function() ``` ##### 方法 4:确保先赋值后使用 ```python def process_data(): # 先赋值再使用 op_points = load_data() # ✅ 先初始化 print(len(op_points)) # 后使用 ``` #### 最佳实践建议 1. **避免全局变量**:优先使用参数传递和返回值(方法2) 2. **初始化变量**:在函数开头初始化所有局部变量 3. **作用域明确**:使用不同的变量名区分全局和局部作用域 4. **代码结构**:保持函数功能单一,减少变量作用域交叉 #### 调试技巧 当遇到此错误时: 1. 检查错误堆栈定位问题行 2. 查找函数内所有同名变量 3. 确认是否存在赋值前读取的情况 4. 使用 `global`/`nonlocal` 或重构代码解决 > 在 Python 中,变量作用域遵循 LEGB 规则:Local(局部)> Enclosing(闭包)> Global(全局)> Built-in(内置)。赋值操作会创建新的局部变量,即使外部存在同名变量[^3][^4]。 ### 相关问题 1. Python 中全局变量和局部变量的作用域规则有何区别? 2. 如何在 Python 闭包函数中正确修改外部作用域的变量? 3. Python 的 `global` 和 `nonlocal` 关键字有哪些使用限制? 4. 有哪些工具可以静态检测 Python 中的作用域问题? 5. Python 的变量作用域规则与其他语言(如 JavaScript)有何不同? [^1]: Python Error - UnboundLocalError: local variable xxx referenced before assignment [^2]: 深度分析python UnboundLocalError: local variable ‘x‘ referenced before assignment [^3]: python UnboundLocalError: local variable 'x' referenced before assignment [^4]: PythonUnboundLocalErrorlocal variable 'xxx' referenced before assignment [^5]: Python问题UnboundLocalErrorlocal variable 'xxx' referenced before assignment
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值