2016-08-18 54 views
2

在執行下面的代碼時,我得到AttributeError: attribute '__doc__' of 'type' objects is not writablefunctools.wrapper - AttributeError:'type'對象的屬性'__doc__'不可寫

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 

即使提供的文檔字符串,我不知道什麼是錯。

它工作正常,如果不包裹,但我需要這樣做。

+0

這不是此特定問題的原因,但你嘗試將裝飾器應用於使用_zero_參數的函數,而不是一個或多個參數 - 因此memoize()的doc字符串似乎是錯誤的。 – martineau

回答

1

@wraps(f)主要是作爲一個函數函數使用,而不是作爲類裝飾器,所以使用它作爲後者可能會導致偶爾的奇怪的怪癖。

您收到特定錯誤消息涉及內建類型的限制有關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),或者使用閉包來管理結果緩存而不是類實例,裝飾器將能夠從基礎函數中複製文檔字符串。

0

您試圖應用於您的類的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 
0

functools.wraps()設計包裝函數,而不是類對象。它所做的一件事就是試圖將包裝(原始)函數的字符串__doc__分配給包裝函數,正如您發現的那樣,它在Python 2中是不允許的。它也對__name____module__屬性。

解決此限制的一種簡單方法是在MemoDict定義爲時手動執行此操作。這是我的意思。 (爲提高可讀性,我總是使用CamelCase類名作爲PEP 8 - Style Guide for Python Code。)

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!