functools.wraps 返回 dict 类型的 __dict__ 而不是 mappingproxy
Posted
技术标签:
【中文标题】functools.wraps 返回 dict 类型的 __dict__ 而不是 mappingproxy【英文标题】:functools.wraps returning a __dict__ of type dict instead of type mappingproxy 【发布时间】:2017-07-04 20:54:29 【问题描述】:这是我在这里的第一个问题,经过长时间的咨询。我在谷歌上搜索了很多关于这个问题的信息,但我认为我没有找到任何有用的信息来解决它。
我正在尝试在 Python 3 中为我的代码创建一个通用的弃用 python 装饰器。目标是正确包装一个函数或类做两件事:打印关于弃用的警告,并在 __doc__
前面加上弃用信息( sphinx
也可以处理它。我想我已经整理出了大部分我想要的东西,我还在为 Sphinx 苦苦挣扎,但我发现了一些对 functools.wraps
的行为感到好奇的东西。基本上,包装类的__dict__
的类型为dict
,而未包装的类的类型为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.wraps
做什么?
您是要包装some_method
吗?因为按原样,您包装了类本身,这种工作方式(没有深入研究,但我相信它应该在您创建实例时发出警告),但这也意味着名称指的是包装函数,而不是类不再是,所以在SomeOldClass
上看不到与类相关的东西,而无需显式钻取到__wrapped__
属性。如果你想以这种方式弃用类,你的包装器应该深入到类中并显式包装__init__
或__new__
(返回原始cls
),而不是返回包装类的函数。
@user2357112 我希望它能够掩盖我的包装功能,所以看起来它是原始类,除了我的更改。具体来说,我希望像 help()
这样的函数能够在我已弃用的类上正常工作。
@ShadowRanger 感谢您的反馈!我可以尝试包装__init__
,没想到。这种方法的问题是我不能轻易地添加__doc__
,可以吗?
@j3mdamas:如果你包装类本身,你可以修改它的文档,然后显式包装它的__init__
并重新分配它。你仍然返回原来的cls
,你只是在改变它的属性。
【参考方案1】:
functools.wrap
不是行为不端,技术上你也没有做错什么,你已经返回了 当调用 将返回实例的函数。
包装后,SomeOldClass
是你包装并返回的函数new_func
:
>>> SomeOldClass
<function __main__.SomeOldClass>
不是班级。未装饰的SomeClass
被保留为__dict__
是mappingproxy
的类。
【讨论】:
感谢您的回答!虽然很难找到装饰类的工作示例,所以我采用了这种方法。【参考方案2】:我强烈建议让你的包装类知道,所以它不会包装类本身:
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
这让你可以在两个类中使用它:
@Deprecated()
class SomeOldClass:
def some_method(self, x, y):
return x + y
不掩盖类行为和功能(可能带有自定义弃用消息):
@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,我只是提供基本示例)。
【讨论】:
非常感谢!这就是我一直在寻找的答案!我将对其进行测试,但我确信它有效。 @j3mdamas:我在显示的案例上对其进行了测试(并确保它可以正确修改__doc__
字符串,class
仍然是一个类等),但我确定有一些边缘情况(例如,我对类与非类的测试在 Py2 上对旧式类不友好,因此您需要对其进行一些调整以支持旧式类)。无论如何,它应该是相当接近的,只需最少的调整。
只是一个小评论:对于方法,__doc__
没有更新。我把作业移到@functools.wraps(wrappedfunc)
之前,它开始工作了。
最后的评论:非常感谢您的示例。完美运行,我希望任何试图装饰班级的人都能找到这个答案。我不需要与 Py2 兼容,所以我很好。关于狮身人面像,我的问题是由于包装器将类变成了一个函数。既然你的代码解决了这个问题,我在 Sphinx 上的所有问题都得到了解决:) 非常感谢!以上是关于functools.wraps 返回 dict 类型的 __dict__ 而不是 mappingproxy的主要内容,如果未能解决你的问题,请参考以下文章