CodeGym /课程 /Python SELF ZH /标准错误,第二部分

标准错误,第二部分

Python SELF ZH
第 20 级 , 课程 2
可用

8.1 Python 作用域规则的误解

Python 的作用域基于所谓的 LEGB 规则,这个缩写的意思是:

  • Local(在函数内部(deflambda)任何方式分配的名字,且没有在此函数中声明为全局的);
  • Enclosing(任何静态包含函数(deflambda)中的本地作用域名字,从内部到外部);
  • Global(在模块文件的顶层分配的名字,或通过在 def 内执行 global 指令);
  • Built-in(在内置名字模块中预先分配的名字:openrangeSyntaxError,等等)。
  • 听起来很简单,对吧?

    但是在 Python 中,这里面有一些细微之处,这让我们面临 Python 编程中的复杂问题。看下面的例子:

    
    x = 10
    def foo():
        x += 1
        print(x)
                
    foo()
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 2, in foo
    UnboundLocalError: local variable 'x' referenced before assignment

    问题出在哪儿?

    上面的错误是因为,当你在作用域中赋值给变量时,Python 会自动认为它是这个作用域的局部变量,并且会隐藏在任何高一级作用域中的同名变量。

    因此,很多人在他们的代码中加入一个赋值操作后,感到惊讶地收到了 UnboundLocalError

    这种特性在使用列表时尤其让开发者迷惑。看看下面的例子:

    
    lst = [1, 2, 3]
    def foo1():
        lst.append(5)  # 这个没问题...
            
    foo1()
    print(lst)
    [1, 2, 3, 5]
            
    lst = [1, 2, 3]
    def foo2():
        lst += [5]  # ... 这个就崩了!
            
    foo2()
    Traceback (most recent call last):
        File "
           
       
           
       
           
       
         ", line 1, in 
        
          File " 
         
           ", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment 
          
         
       

    为什么 foo2 崩掉了,而 foo1 却正常工作?

    答案和之前的例子一样,但是在这里情况更微妙。foo1 不对 lst 应用赋值运算符,而 foo2 做了。记住,lst += [5] 实际上是 lst = lst + [5] 的简写,我们可以看到我们试图为 lst 赋值(因此 Python 认为它在局部作用域中)。然而,我们想要赋给 lst 的值基于 lst 本身(再次,现在假设它在局部作用域中),而这还没有被定义。所以我们得到一个错误。

    8.2 在迭代列表时修改它

    下面这段代码中的问题应该很明显:

    
    odd = lambda x: bool(x % 2)
    numbers = [n for n in range(10)]
        
    for i in range(len(numbers)):
        if odd(numbers[i]):
            del numbers[i]  # 糟糕:在迭代列表时删除项目
        
    Traceback (most recent call last):
            File "
           
       
           
       
           
       
         ", line 2, in 
        
          IndexError: list index out of range 
         
       

    在迭代列表或数组时删除它的元素是 Python 中一个众所周知的问题,任何有经验的软件开发人员都很熟悉这个问题。但尽管上面的例子可能很明显,甚至经验丰富的开发人员在更复杂的代码中也可能踩到这个坑。

    幸运的是,Python 包含了一系列优雅的编程范式,当正确使用时,可以显著简化和优化代码。另一个令人愉快的后果是,在更简单的代码中,意外删除迭代列表元素的错误的可能性要小得多。

    其中一个范式是列表生成器。此外,理解列表生成器的工作原理特别有助于避免这个特定问题,如下所示的替代代码实现就能很好地工作:

    
    odd = lambda x: bool(x % 2)
    numbers = [n for n in range(10)]
    numbers[:] = [n for n in numbers if not odd(n)]  # 只是选择新的元素
    print(numbers)
    # [0, 2, 4, 6, 8]

    重要! 这里并没有分配新的列表对象。使用numbers[:] 是批量 分配新值给列表的所有元素。

    8.3 不理解 Python 如何在闭包中绑定变量

    看下面的例子:

    
    def create_multipliers():
        return [lambda x: i * x for i in range(5)]  #返回函数列表!
    
    for multiplier in create_multipliers():
        print(multiplier(2))

    你可能期待如下输出:

    
    0
    2
    4
    6
    8
    

    但实际上你会得到:

    
    8
    8
    8
    8
    8
    

    惊不惊喜?

    这是因为 Python 的晚绑定,这意味着在闭包中使用的变量的值会在内部函数调用时才搜索。

    因此在上面的代码中,每当调用任何一个返回的函数时,i 的值会在调用时在周围作用域中查找(那时循环已经完成,所以 i 被赋予了最终结果—4)。

    解决这个 Python 常见问题的方法是:

    
    def create_multipliers():
        return [lambda x, i = i : i * x for i in range(5)]
    for multiplier in create_multipliers():
        print(multiplier(2))
     # 0 # 2 # 4 # 6 # 8

    瞧!我们在这里使用默认参数来为匿名函数生成达到我们期望的行为。有人会称这是优雅的解决方案。有人会觉得微妙。有人讨厌这样的东西。但如果你是一个 Python 开发者,这种理解是至关重要的。

2
任务
Python SELF ZH, 第 20 级, 课程 2
已锁定
修正全局变量。
修正全局变量。
2
任务
Python SELF ZH, 第 20 级, 课程 2
已锁定
修正闭包。
修正闭包。
评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Anonymous #11738799 级别 47,Shanghai,China
3 一月 2026
各位加油,我才知道python连高中都是标准课程了,让我们一起学会它吧