装饰器中不同类型的变量是否有不同的作用域? (Python)
Are there different scopes for different types of variables in a decorator? (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 的范围内?
为什么字典没有和整数一样的问题?
提前致谢!
我认为 "read-only" 你的意思是你不能将名称重新绑定到非本地范围内的对象(这在 Python 2 中是正确的)。对于您的字典示例,您没有将名称重新绑定到对象——您正在改变字典实例——d[0] += 1
要求 d
对象本身检索与键 0 关联的值,并且然后要求对象放回加法的结果。
所以你对 dict 所做的与你对 int 所做的在本质上是不同的,这就是为什么你认为字典不会遇到与 int 相同的问题。这种误解经常出现,因为字典是可变的,代码可以很容易地改变字典的内容,程序员很容易陷入陷阱,认为他们在真正重新绑定整数时同样改变了整数的内容。
整数根据定义是不可变的——你不能改变它们——所以为了"change"一个整数变量,你总是必须将标识符重新绑定到一个不同的对象。但是如果你反弹了 dict,例如使用像 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
原因是像+=
这样的操作的性质是由左边的目标类型决定的,所以var += 1
和[=不是一回事12=]。如果左侧是裸名,则这是一个变量重新绑定。如果不是,那就不是。在d[0] += 1
的情况下,+=
由字典对象处理,不涉及重新绑定名称d
。
相关文档是here。请注意第一个要点之间的区别,其中结果只是 "the name is bound",而所有其他要点的结果是 "the object is asked to...".
我很难理解 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 的范围内?
为什么字典没有和整数一样的问题?
提前致谢!
我认为 "read-only" 你的意思是你不能将名称重新绑定到非本地范围内的对象(这在 Python 2 中是正确的)。对于您的字典示例,您没有将名称重新绑定到对象——您正在改变字典实例——d[0] += 1
要求 d
对象本身检索与键 0 关联的值,并且然后要求对象放回加法的结果。
所以你对 dict 所做的与你对 int 所做的在本质上是不同的,这就是为什么你认为字典不会遇到与 int 相同的问题。这种误解经常出现,因为字典是可变的,代码可以很容易地改变字典的内容,程序员很容易陷入陷阱,认为他们在真正重新绑定整数时同样改变了整数的内容。
整数根据定义是不可变的——你不能改变它们——所以为了"change"一个整数变量,你总是必须将标识符重新绑定到一个不同的对象。但是如果你反弹了 dict,例如使用像 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
原因是像+=
这样的操作的性质是由左边的目标类型决定的,所以var += 1
和[=不是一回事12=]。如果左侧是裸名,则这是一个变量重新绑定。如果不是,那就不是。在d[0] += 1
的情况下,+=
由字典对象处理,不涉及重新绑定名称d
。
相关文档是here。请注意第一个要点之间的区别,其中结果只是 "the name is bound",而所有其他要点的结果是 "the object is asked to...".