functools.wraps 返回一个 __dict__ 类型的 dict 而不是类型 mappingproxy

functools.wraps returning a __dict__ of type dict instead of type mappingproxy

这是我的第一个问题,经过长时间的咨询。我在谷歌上搜索了很多关于这个问题的信息,但我想我没有找到解决它的任何有用信息。

我正在尝试为我在 Python 3 中的代码创建一个通用的弃用 python 装饰器。 objective 是正确包装一个函数或 class 做两个事情:打印关于弃用的警告,并在 __doc__ 前面加上弃用信息(它也可以由 sphinx 处理)。我想我已经整理出了大部分我想要的东西,我仍然在与 Sphinx 作斗争,但我发现了一些对 functools.wraps 的行为感到好奇的东西。基本上,包装的 class 的 __dict__dict 类型,而未包装的 class 的类型是 mappingproxy.

这是一些展示代码:

import functools

class Deprecated(object):
    def __call__(self, cls):
        myassigns = functools.WRAPPER_ASSIGNMENTS
        myassigns += ('__mro__',)

        @functools.wraps(cls, assigned=myassigns)
        def new_func(*args, **kwargs):
            warnings.warn(message, category=DeprecationWarning, stacklevel=2)
            return cls(*args, **kwargs)

        return new_func


@Deprecated()
class SomeOldClass:
    def some_method(self, x, y):
        return x + y

class SomeClass:
    def some_method(self, x, y):
        return x + y

print(type(SomeOldClass.__dict__))
print(type(SomeClass.__dict__))

并且输出:

<class 'dict'>
<class 'mappingproxy'>

functools.wraps 行为不当还是我做错了什么?

functools.wrap 没有行为不端,您在技术上也没有做错什么,您已经 return 编辑了 调用时 将 return 实例。

包装后,SomeOldClass 是您包装并 return 编辑的函数 new_func

>>> SomeOldClass
<function __main__.SomeOldClass>

不是 class。 un-decorated SomeClass 被保存为 class 谁的 __dict__mappingproxy.

我强烈建议让你的包装器 class 知道,这样它就不会包装 class 本身:

class Deprecated(object):
    def __init__(self, messagefmt='{wrapped} is deprecated'):
        self.messagefmt = messagefmt

    def __call__(self, wrapped):
        message = self.messagefmt.format(wrapped=wrapped.__name__)
        wrappingclass = isinstance(wrapped, type)
        if wrappingclass:
            wrappedfunc = wrapped.__init__
        else:
            wrappedfunc = wrapped

        @functools.wraps(wrappedfunc)
        def new_func(*args, **kwargs):
            warnings.warn(message, category=DeprecationWarning, stacklevel=2)
            return wrappedfunc(*args, **kwargs)
        wrapped.__doc__ = 'Deprecated\n\n' + message + '\n\n' + (wrapped.__doc__ or '')

        if wrappingclass:
            wrapped.__init__ = new_func
            return wrapped
        else:
            return new_func

这让您可以将它与 classes 一起使用:

 @Deprecated()
 class SomeOldClass:
     def some_method(self, x, y):
         return x + y

不掩盖 class 行为和功能(可能带有自定义弃用消息):

 @Deprecated('foo is dead')
 def foo():
     pass

所以当你使用它们时:

 >>> import warnings
 >>> warnings.filterwarnings('always', category=DeprecationWarning)
 >>> SomeOldClass()
 /usr/local/bin/ipython3:1: DeprecationWarning: SomeOldClass is deprecated
   #!/usr/bin/python3
 <__main__.SomeOldClass at 0x7fea8f744a20>
 >>> foo()
 /usr/local/bin/ipython3:1: DeprecationWarning: foo is dead
   #!/usr/bin/python3

同样,两者的 __doc__ 都被修改为在弃用通知之前添加(您可以将其调整为 Sphinxify,我只是提供了基本示例)。