2014-10-16 83 views
1

此代码不起作用:的Python:嵌套函数和变量的作用域

def lol(): 
    i = 1 
    def _lol(): 
     i += 1 
    _lol() 
lol() 

错误:

local variable 'i' referenced before assignment 

但是,下面的代码工作正常:

def lol(): 
    i = [1] 
    def _lol(): 
     i[0] += 1 
    _lol() 
lol() 

为什么那?

回答

4

Python范围适合3类 - local,nonlocalglobal。默认情况下,函数只能在本地作用域中更改引用(引用是使用赋值运算符创建的)。

你可以自由地发生变异你有一个参考的对象这就是为什么第二个例子作品(i是列表[1]的引用,那么你改变/突变它的第一项)。总之,你是突变i引用的对象,你不是试图改变引用。需要注意的是,你可以给一个函数访问通过global关键字来改变在全球范围内的参考:

i = 1 
def func(): 
    global i # If you comment this out, i doesn't get changed in the global scope 
    i = 2 

func() 
print(i) # 2 -- 1 if the global statement is commented out. 

注意python3.x添加nonlocal关键字。它与global的作用相同,但与非本地范围相同。例如

def foo(): 
    i = 1 # nonlocal to bar 
    def bar(): 
     nonlocal i 
     print(i) 
     i += 1 
    return bar 

bar1 = foo() 
bar1() # 1 
bar1() # 2 
bar1() # 3 

bar2 = foo() 
bar2() # 1 
bar2() # 2 

bar1() # 4 bar2 doesn't influence bar1 at all. 

增强运营商

这是更先进一些,但提供给希望能帮助回答有关像+=运营商的问题。考虑这样的情况:

x = [] 
def func(): 
    x += [1] 

你可能认为这个工作 - 毕竟,x += [1]的列表x真的只是x.extend([1])吧?。不幸的是,这并不完全。我们可以使用dis.dis反汇编func以查看更多信息。

>>> dis.dis(func) 
    2   0 LOAD_FAST    0 (x) 
       3 LOAD_CONST    1 (1) 
       6 BUILD_LIST    1 
       9 INPLACE_ADD   
      10 STORE_FAST    0 (x)  ### IMPORTANT! 
      13 LOAD_CONST    0 (None) 
      16 RETURN_VALUE   

注意字节码指令STORE_FAST?基本上说,将INPLACE_ADD的结果存储在本地字典中的名称x中。换句话说,你写的:

x += [1] 

但是Python执行:

x = x.__iadd__([1]) 

为什么? __iadd__应该在适当的位置操作,为什么它需要重新命名为__iadd__的返回值?重新绑定的部分是问题 - 即这段代码工作:

x = [] 
def func(): 
    x.__iadd__([1]) 

答案是因为Python有不可变对象和__iadd__需要与他们合作过。因此,__iadd__可以返回“self”以外的对象。这最终是非常有用的。考虑i = 1; i += 1。此调用仅适用于int.__iadd__被允许返回一个新的整数。

在更深入的讨论其实这是我的所有时间在计算器上最upvoted答案,可以发现here

+0

你能不能也包括这样的:'我= []; def x():i + = [2]' - 为什么这不起作用?列表上的'+ ='不会重新分配任何东西...... – georg 2014-10-16 16:09:24

+0

@georg - 当然可以。该任务只是有点隐藏。如果你使用'dis'反汇编这个函数,你会看到'STORE_FAST ...(i)'指令。 'i + = something'调用'i .__ iadd __(something)',并将返回值存储在本地范围内的名称'i'中。如果没有,'+ ='不适用于不可变对象(即'int')。注意['cls .__ iadd__'应该(但不一定)为可变对象返回'self',为不可变对象返回一个新实例](https://docs.python.org/2/reference/datamodel.html #object .__ iadd__)。 – mgilson 2014-10-16 16:17:27

+0

谢谢))我的意思是,你不认为这是值得添加到你的答案? – georg 2014-10-16 16:24:33