装饰器中不同类型的变量是否有不同的作用域?(Python)



我很难理解python装饰器中变量的作用域。我在某个地方读到,非局部变量存储为只读。但是字典似乎是个例外。

def outer(f):
    def inner():
        print val
        return f()
    val =1
    return inner
def outer2(f):
    def inner2():
        val+=1
        print val
        return f()
    val =1
    return inner2
def outer3(f):
    def inner3():
        d[0]+=1
        print d
        return f()
    d ={0:0}
    return inner3
import doctest
class Test: """
>>> function = lambda : 'Function called'
>>> f1=outer(function)
>>> f1()
1
'Function called'
>>> f2=outer2(function)
>>> f2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in inner2
UnboundLocalError: local variable 'val' referenced before assignment
>>> f3=outer3(function)
>>> f3()
{0: 1}
'Function called'
"""
print (doctest.testmod())

为什么val不在f2的作用域中?
为什么字典和整数没有同样的问题?

提前感谢!

原因是,像+=这样的操作的性质是由左侧目标的类型决定的,因此var += 1var[blah] += 1不是同一种东西。如果左边是一个名称,这是一个变量重绑定。如果不是,那就不是。在d[0] += 1的情况下,+=由字典对象处理,不涉及重新绑定名称d

相关文档在这里。请注意,第一个项目符号点的结果是"名称被绑定",而其他所有项目符号点的结果是"对象被要求…"。

我认为"只读"的意思是你不能在非局部范围内将名称重新绑定到对象(这在Python 2中是正确的)。在你的字典示例中,你没有将名称重新绑定到对象—你正在改变字典实例—d[0] += 1要求d对象本身检索与键值0相关的值,然后要求对象将添加的结果放回。

所以你对字典所做的和你对整型所做的在性质上是不同的,这就是为什么你认为字典不会像整型那样遇到同样的问题。这种误解经常出现,因为字典是可变的,代码可以很容易地改变字典的内容,程序员很容易陷入这样的陷阱:当他们真正重新绑定整数时,他们认为他们同样是在改变整数的内容。

整数根据定义是不可变的——你不能改变它们——所以为了"改变"一个整数变量,你总是必须将标识符重新绑定到一个不同的对象。但是,如果您对字典进行反弹,例如使用像d = d.copy()这样的代码,您就会看到同样的问题。

在Python中,变量的id是它的内存地址——不同的地址,不同的对象。正如你在这里看到的,当你修改列表中的一个元素时,你仍然有相同的列表对象,但当你修改一个整数时——你并没有真正修改它——你得到了一个不同的对象:

>>> x = [0]
>>> id(x), id(x[0])
(3063883308, 138405104)
>>> x[0] += 1
>>> id(x), id(x[0])
(3063883308, 138405120)

正如BrenBarn在下面的回答中指出的那样,我最初误解了你的问题的全部范围,因为我关注的是你最初的困惑,为什么有些东西在字典实例中的项上工作,而在直接使用int时却不起作用。

在函数内部,对裸变量的赋值(例如不在对象上调用__setattr__之类的函数)总是对局部变量进行赋值,除非它们声明为global或(在Python 3中)nonlocal

增强赋值(如+=)遵循与赋值相同的规则,除了它们自然要求初始值在可以修改之前首先存在。因此,当对一个未声明为非局部或全局的变量进行赋值时,它必须是一个局部变量,当它不存在(还没有被前面的语句绑定)以便可以在增广赋值表达式中使用时,将引发您看到的异常。通过添加nonlocal语句,可以使失败的示例与Python 3一起工作:

>>> def outer2(f):
...     def inner2():
...         nonlocal val
...         val+=1
...         print(val)
...         return f()
...     val = 1
...     return inner2
... 
>>> function = lambda : 'Function called'
>>> 
>>> print(outer2(function)())
2
Function called

最新更新