在下面的代码中,当g.__int
和0
同时保留1
和g.__dictionary
时,为什么g.__list
等于import functools
def dictify(func):
func.__dictionary = { 0 : 0 }
@functools.wraps(func)
def _func(*args, **kwds):
func.__dictionary[0] += 1
print(' Incremented __dictionary, now __dictionary = {0}'.format(str(func.__dictionary)))
return func(*args, **kwds)
return _func
def listify(func):
func.__list = [1, 2, 3]
@functools.wraps(func)
def _func(*args, **kwds):
func.__list.append(0)
print(' Appended 0 to __list, now __list = {0}'.format(str(func.__list)))
return func(*args, **kwds)
return _func
def intify(func):
func.__int = 0
@functools.wraps(func)
def _func(*args, **kwds):
func.__int += 1
print(' Incremented __int, now __int = {0}'.format(func.__int))
return func(*args, **kwds)
return _func
def g():
return 'pennyroyal tea'
print('*** UNMODIFIED ***')
print('g() returns \'{0}\''.format(g()))
print('id(g) = {0}'.format(id(g)))
g = dictify(g)
print('*** DICTIFIED ***')
print('g() returns \'{0}\''.format(g()))
print('g.__dictionary = {0}'.format(str(g.__dictionary)))
print('id(g) = {0}'.format(id(g)))
g = listify(g)
print('*** LISTIFIED ***')
print('g() returns \'{0}\''.format(g()))
print('g.__dictionary = {0}'.format(str(g.__dictionary)))
print('g.__list = {0}'.format(str(g.__list)))
print('id(g) = {0}'.format(id(g)))
g = intify(g)
print('*** INTIFIED ***')
print('g() returns \'{0}\''.format(g()))
print('g.__dictionary = {0}'.format(str(g.__dictionary)))
print('g.__list = {0}'.format(str(g.__list)))
print('g.__int = {0}'.format(str(g.__int)))
print('id(g) = {0}'.format(id(g)))
而不是*** UNMODIFIED ***
g() returns 'pennyroyal tea'
id(g) = 139861398390976
*** DICTIFIED ***
Incremented __dictionary, now __dictionary = {0: 1}
g() returns 'pennyroyal tea'
g.__dictionary = {0: 1}
id(g) = 139861398391096
*** LISTIFIED ***
Appended 0 to __list, now __list = [1, 2, 3, 0]
Incremented __dictionary, now __dictionary = {0: 2}
g() returns 'pennyroyal tea'
g.__dictionary = {0: 2}
g.__list = [1, 2, 3, 0]
id(g) = 139861398391216
*** INTIFIED ***
Incremented __int, now __int = 1
Appended 0 to __list, now __list = [1, 2, 3, 0, 0]
Incremented __dictionary, now __dictionary = {0: 3}
g() returns 'pennyroyal tea'
g.__dictionary = {0: 3}
g.__list = [1, 2, 3, 0, 0]
g.__int = 0
id(g) = 139861398391336
?他们在装饰者里面有什么价值?
为什么我可以将一个列表/字典作为属性添加到装饰器内的函数,然后在装饰器外部访问它,但我不能对整数做同样的事情?
以下是一些代码来说明:
func.__int
这给出了以下输出:
1
您可以在装饰器中看到g.__int
的值打印为0
,但在装饰器外,g.__dictionary
是默认g.__list
,但是{{ 1}}和id(g)
在装饰者内部和外部都被引用时保留其值。
注意: dictify()
调用显示,使用listify()
,intify()
和{{1}}进行装饰都会返回新对象,说明功能是不可改变的。 (参见我的详细解释here)
此问题基于我之前的here。我的实际需要得到了答案的满足,但我的'为什么'的本能不会让这一点孤立。 :)
答案 0 :(得分:3)
将传递函数的属性分配给装饰器,但是返回不同的函数(包装函数)。 functools.wraps
浅层复制属性从一个到另一个,这意味着它复制列表和dict对象。然后你改变这些对象。但你不能改变int,所以你要做的就是更改g
的“解包”版本的值,同时打印包装版本的值。
这是一个尝试说明的事情:
>>> def g():
... return 'pennyroyal tea'
>>> f = intify(g)
>>> f()
Incremented __int, now __int = 1
'pennyroyal tea'
>>> f.__int
0
>>> g.__int
1
我启发了g
,但已将其分配给f
。您可以看到__int
属性 已更新---但是在原始函数上,而不是已包装的。
您没有看到与list和dict的区别,因为这些对象是可变的。有一个列表和一个dict,两个函数共享。但是,如果您逐个拆分包装函数,则可以再次看到它:
>>> f = dictify(g)
... f2 = listify(f)
... f3 = intify(f2)
>>> f3()
Incremented __int, now __int = 1
Appended 0 to __list, now __list = [1, 2, 3, 0]
Incremented __dictionary, now __dictionary = {0: 1}
'pennyroyal tea'
>>> f3.__list is f2.__list
True
>>> f3.__dictionary is f2.__dictionary
True
>>> f3.__int is f2.__int
False
您对__list
和__dictionary
的修改会改变对象,但您对__int
的修改会创建一个 new int(因为ints不能变异) ,在传递给装饰器的函数的__int
属性与它返回的包装函数之间创建一个分隔。
这里的基本问题是你在想要在装饰器中做的事情是thisFuncion.__list.append(0)
,其中thisFunction
是返回的,装饰的函数,而不是装饰功能。也就是说,您希望包装器能够引用自身。但你不能这样做。 Python中没有通用的方法来引用函数。在装饰器中,您定义了一个引用函数_func
的函数func
。有两种不同的功能,_func
仅在func
上设置属性,而不是自身。
当然,真正的问题是为什么你首先尝试设置这样的函数属性。但是我从你的问题中得知,你只是出于好奇而要求了解发生了什么,而不是因为你真的想要这样做。
答案 1 :(得分:1)
添加到@ BrenBarn的答案,如果您编写自己的functools.wraps
版本,则必须执行以下操作:
def intify(func):
print func is g
func.__int = 0
def _func(*args, **kwds):
func.__int += 1
print(' Incremented __int, now __int = {0}'.format(func.__int))
return func(*args, **kwds)
_func.__int = func.__int
_func.__doc__ = func.__doc__
#... and some more
return _func
即您必须将整数值分配给新的函数对象:
_func.__int = func.__int
但是,由于整数是不可变的,因此更改一个引用不会影响另一个引用:
>>> x = 1
>>> y = x
>>> x += 1
>>> x
2
>>> y #y still unchanged
1
但是,当您执行就地操作时,同样的事情不适用于可变对象:
>>> x = [1]
>>> y = x
>>> y.append(10)
>>> x
[1, 10]
>>> y
[1, 10]
>>>
从source code您可以看到wraps
只是将相同的对象分配给新的函数对象,因此当您使用可变对象时会产生副作用。
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper