【问题标题】:functools.wrapper - AttributeError: attribute '__doc__' of 'type' objects is not writablefunctools.wrapper - AttributeError:“类型”对象的属性“__doc__”不可写
【发布时间】:2016-12-24 22:07:54
【问题描述】:

在执行下面的代码时,我收到了AttributeError: attribute '__doc__' of 'type' objects is not writable

from functools import wraps

def memoize(f):
    """ Memoization decorator for functions taking one or more arguments.
        Saves repeated api calls for a given value, by caching it.
    """
    @wraps(f)
    class memodict(dict):
       """memodict"""
       def __init__(self, f):
           self.f = f
       def __call__(self, *args):
           return self[args]
       def __missing__(self, key):
           ret = self[key] = self.f(*key)
           return ret
     return memodict(f)

@memoize
def a():
    """blah"""
    pass

追溯:

AttributeError Traceback (most recent call last)
<ipython-input-37-2afb130b1dd6> in <module>()
     17             return ret
     18     return memodict(f)
---> 19 @memoize
     20 def a():
     21     """blah"""

<ipython-input-37-2afb130b1dd6> in memoize(f)
      7     """
      8     @wraps(f)
----> 9     class memodict(dict):
     10         """memodict"""
     11         def __init__(self, f):

/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.pyc in update_wrapper(wrapper, wrapped, assigned, updated)
     31     """
     32     for attr in assigned:
---> 33         setattr(wrapper, attr, getattr(wrapped, attr))
     34     for attr in updated:
     35         getattr(wrapper, attr).update(getattr(wrapped, attr, {}))

AttributeError: attribute '__doc__' of 'type' objects is not writable

即使提供了文档字符串,我也不知道这有什么问题。

如果不包装也可以,但我需要这样做。

【问题讨论】:

  • 这不是这个特定问题的原因,但您正试图将装饰器应用于一个接受 zero 参数的函数,而不是一个或多个参数——所以文档memoize() 的字符串似乎是错误的。

标签: python-2.7 decorator functools dictionary-missing


【解决方案1】:

functools.wraps() 旨在包装函数,而不是类对象。它所做的一件事是尝试将包装(原始)函数的 __doc__ 字符串分配给包装函数,正如您所发现的,这在 Python 2 中是不允许的。它对__name____module__ 属性。

解决此限制的一种简单方法是在定义MemoDict 时手动执行此操作。这就是我的意思。 (注意为了提高可读性,我总是按照PEP 8 - Style Guide for Python Code 使用CamelCase 类名。)

def memoize(f):
    """ Memoization decorator for functions taking one or more arguments.
        Saves repeated api calls for a given value, by caching it.
    """
    class MemoDict(dict):
        __doc__ = f.__doc__
        __name__ = f.__name__
        __module__ = f.__module__

        def __init__(self, f):
            self.f = f
        def __call__(self, *args):
            return self[args]
        def __missing__(self, key):
            ret = self[key] = self.f(*key)
            return ret

    return MemoDict(f)

@memoize
def a():
    """blah"""
    print('Hello world!')

print(a.__doc__)     # -> blah
print(a.__name__)    # -> a
print(a.__module__)  # -> __main__
a()                  # -> Hello world!

事实上,如果您愿意,您可以创建自己的包装器/类装饰函数来做到这一点:

def wrap(f):
    """ Convenience function to copy function attributes to derived class. """
    def class_decorator(cls):
        class Derived(cls):
            __doc__ = f.__doc__
            __name__ = f.__name__
            __module__ = f.__module__
        return Derived

    return class_decorator

def memoize(f):
    """ Memoization decorator for functions taking one or more arguments.
        Saves repeated api calls for a given value, by caching it.
    """
    @wrap(f)
    class MemoDict(dict):
        def __init__(self, f):
            self.f = f
        def __call__(self, *args):
            return self[args]
        def __missing__(self, key):
            ret = self[key] = self.f(*key)
            return ret

    return MemoDict(f)

@memoize
def a():
    """blah"""
    print('Hello world!')

print(a.__doc__)     # -> blah
print(a.__name__)    # -> a
print(a.__module__)  # -> __main__
a()                  # -> Hello world!

【讨论】:

    【解决方案2】:

    您尝试应用到您的类的 wraps 装饰器不起作用,因为您无法在创建类后修改其文档字符串。您可以使用此代码重新创建错误:

    class Foo(object):
        """inital docstring"""
    
    Foo.__doc__ = """new docstring""" # raises an exception in Python 2
    

    Python 3 中不会发生异常(我不确定为什么会更改)。

    一种解决方法可能是在您的类中分配类变量__doc__,而不是在类存在后使用wraps 来设置文档字符串:

    def memoize(f):
        """ Memoization decorator for functions taking one or more arguments.
            Saves repeated api calls for a given value, by caching it.
        """
        class memodict(dict):
           __doc__ = f.__doc__  # copy docstring to class variable
           def __init__(self, f):
               self.f = f
           def __call__(self, *args):
               return self[args]
           def __missing__(self, key):
               ret = self[key] = self.f(*key)
               return ret
         return memodict(f)
    

    这不会复制wraps 尝试复制的任何其他属性(如__name__ 等)。如果它们对您很重要,您可能想自己解决这些问题。 __name__ 属性需要在创建类之后设置(你不能在类定义中分配它):

    class Foo(object):
        __name__ = "Bar" # this has no effect
    
    Foo.__name__ = "Bar" # this works
    

    【讨论】:

      【解决方案3】:

      @wraps(f) 主要被设计为用作 function 装饰器,而不是用作类装饰器,因此将其用作后者可能会导致偶尔出现奇怪的怪癖。

      您收到的具体错误消息与 Python 2 上的内置类型限制有关:

      >>> class C(object): pass
      ... 
      >>> C.__doc__ = "Not allowed"
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: attribute '__doc__' of 'type' objects is not writable
      

      如果您使用 Python 3,请切换到 Python 2 中的经典类(通过继承 UserDict.UserDict 而不是 dict 内置),或者使用闭包而不是类实例来管理结果缓存,装饰器将能够从底层函数复制文档字符串。

      【讨论】:

      • 能否请您举例说明“切换到 Python 2 中的经典类或使用闭包来管理结果缓存”!
      猜你喜欢
      • 1970-01-01
      • 2019-09-13
      • 2018-01-29
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      • 2021-11-09
      • 2019-06-08
      相关资源
      最近更新 更多