为什么list / dict属性会保留装饰器外的值,但整数属性不会?

时间:2014-01-08 07:37:54

标签: python function attributes decorator

在下面的代码中,当g.__int0同时保留1g.__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。我的实际需要得到了答案的满足,但我的'为什么'的本能不会让这一点孤立。 :)

2 个答案:

答案 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