我一直在尝试获取我的文档,以便我正在开发一个包含镜像客户端和服务器API的开源项目。为此,我创建了一个装饰器,大部分时间都可以用来记录一个简单地对其输入执行验证的方法。您可以找到一个包含这些方法的类here和装饰器的实现here。
正如您所见,装饰器使用functools.wraps
来保留文档字符串,我还想到了签名,但源代码与生成的文档看起来像这样:
资料来源:
VS
文档:
有没有人知道让setH
生成的文档显示正确的呼叫签名的方法是什么? (没有为每个签名设置一个新的装饰器 - 我需要镜像的方法)
我找到了一个解决方法,其中涉及让装饰器不更改未绑定方法,但让类在绑定时改变方法(对象实例化) - 这看起来像是一个黑客,所以对此有任何意见,或替代这样做的方式,将不胜感激。
答案 0 :(得分:4)
我想避免依赖标准库之外的太脏,所以当我看了Decorator模块时,我主要试图重现它的功能......不成功......
所以我从另一个角度看问题,现在我有一个部分工作的解决方案,主要是通过查看this commit来描述。它并不完美,因为它依赖于使用部分内容,这些内容在REPL中有所帮助。我们的想法是,不是替换应用装饰器的函数,而是使用属性进行扩充。
+def s_repr(obj):
+ """ :param obj: object """
+ return (repr(obj) if not isinstance(obj, SikuliClass)
+ else "self._get_jython_object(%r)" % obj._str_get)
+
+
def run_on_remote(func):
...
- func.s_repr = lambda obj: (repr(obj)
- if not isinstance(obj, SikuliClass) else
- "self._get_jython_object(%r)" % obj._str_get)
-
- def _inner(self, *args):
- return self.remote._eval("self._get_jython_object(%r).%s(%s)" % (
- self._id,
- func.__name__,
- ', '.join([func.s_repr(x) for x in args])))
-
- func.func = _inner
+ gjo = "self._get_jython_object"
+ func._augment = {
+ 'inner': lambda self, *args: (self.remote._eval("%s(%r).%s(%s)"
+ % (gjo, self._id, func.__name__,
+ ', '.join([s_repr(x)for x in args]))))
+ }
@wraps(func)
def _outer(self, *args, **kwargs):
func(self, *args, **kwargs)
- if hasattr(func, "arg"):
- args, kwargs = func.arg(*args, **kwargs), {}
- result = func.func(*args, **kwargs)
- if hasattr(func, "post"):
+ if "arg" in func._augment:
+ args, kwargs = func._augment["arg"](self, *args, **kwargs), {}
+ result = func._augment['inner'](self, *args, **kwargs)
+ if "post" in func._augment:
return func.post(result)
else:
return result
def _arg(arg_func):
- func.arg = arg_func
- return _outer
+ func._augment['arg'] = arg_func
+ return func
def _post(post_func):
- func.post = post_func
- return _outer
+ func._augment['post'] = post_func
+ return func
def _func(func_func):
- func.func = func_func
- return _outer
- _outer.arg = _arg
- _outer.post = _post
- _outer.func = _func
- return _outer
+ func._augment['inner'] = func_func
+ return func
+
+ func.arg = _outer.arg = _arg
+ func.post = _outer.post = _post
+ func.func = _outer.func = _func
+ func.run = _outer.run = _outer
+ return func
所以这实际上并没有改变未绑定的方法,因此生成的文档保持不变。诡计的第二部分发生在课堂初始化。
class ClientSikuliClass(ServerSikuliClass):
""" Base class for types based on the Sikuli native types """
...
def __init__(self, remote, server_id, *args, **kwargs):
"""
:type server_id: int
:type remote: SikuliClient
"""
super(ClientSikuliClass, self).__init__(None)
+ for key in dir(self):
+ try:
+ func = getattr(self, key)
+ except AttributeError:
+ pass
+ else:
+ try:
+ from functools import partial, wraps
+ run = wraps(func.run)(partial(func.run, self))
+ setattr(self, key, run)
+ except AttributeError:
+ pass
self.remote = remote
self.server_id = server_id
因此,在实例化任何继承ClientSikuliClass
的类的实例时,会尝试获取该实例的每个属性的run属性,并在尝试获取该属性时返回返回的内容,所以绑定的方法
现在是部分应用的_outer
函数。
所以这个问题很多:
run
属性...... 因此,虽然我对自己的问题有了答案,但我对此并不满意。
好的,经过多一点工作,我最终得到了这个:
class ClientSikuliClass(ServerSikuliClass):
""" Base class for types based on the Sikuli native types """
...
def __init__(self, remote, server_id, *args, **kwargs):
"""
:type server_id: int
:type remote: SikuliClient
"""
super(ClientSikuliClass, self).__init__(None)
- for key in dir(self):
+
+ def _apply_key(key):
try:
func = getattr(self, key)
+ aug = func._augment
+ runner = func.run
except AttributeError:
- pass
- else:
- try:
- from functools import partial, wraps
- run = wraps(func.run)(partial(func.run, self))
- setattr(self, key, run)
- except AttributeError:
- pass
+ return
+
+ @wraps(func)
+ def _outer(*args, **kwargs):
+ return runner(self, *args, **kwargs)
+
+ setattr(self, key, _outer)
+
+ for key in dir(self):
+ _apply_key(key)
+
self.remote = remote
self.server_id = server_id
这可以防止丢失对象上的文档。您还将看到func._augment属性被访问,即使它未被使用,因此如果它不存在,则不会触及对象属性。
如果有人对此有任何意见,我会感兴趣吗?
答案 1 :(得分:4)
在PRAW中,我通过让条件装饰器在发生狮身人面像构建时返回原始函数(而不是修饰函数)来处理这个问题。
在PRAW的sphinx conf.py中,我添加了以下内容作为确定SPHINX目前是否正在构建的方法:
import os
os.environ['SPHINX_BUILD'] = '1'
然后在PRAW中,它的装饰器看起来像:
import os
# Don't decorate functions when building the documentation
IS_SPHINX_BUILD = bool(os.getenv('SPHINX_BUILD'))
def limit_chars(function):
"""Truncate the string returned from a function and return the result."""
@wraps(function)
def wrapped(self, *args, **kwargs):
output_string = function(self, *args, **kwargs)
if len(output_string) > MAX_CHARS:
output_string = output_string[:MAX_CHARS - 3] + '...'
return output_string
return function if IS_SPHINX_BUILD else wrapped
return function if IS_SPHINX_BUILD else wrapped
行允许SPHINX选择正确的签名。
答案 2 :(得分:3)
functools.wraps
仅保留__name__
,__doc__
和__module__
。要保留签名,请查看Michele Simionato的Decorator module。
答案 3 :(得分:2)
要扩展我对Ethan's answer的简短评论,这是我使用functools
包的原始代码:
import functools
def trace(f):
"""The trace decorator."""
logger = ... # some code to determine the right logger
where = ... # some code to create a string describing where we are
@functools.wraps(f)
def _trace(*args, **kwargs):
logger.debug("Entering %s", where)
result = f(*args, **kwargs)
logger.debug("Leaving %s", where)
return result
return _trace
这里是使用decorator
包的代码:
import decorator
def trace(f):
"""The trace decorator."""
logger = ... # some code to determine the right logger
where = ... # some code to create a string describing where we are
def _trace(f, *args, **kwargs):
logger.debug("Entering %s", where)
result = f(*args, **kwargs)
logger.debug("Leaving %s", where)
return result
return decorator.decorate(f, _trace)
出于性能原因,我们希望移动代码以确定正确的记录器和实际函数包装器中的where-string。因此,在两个版本中都使用嵌套包装函数。
这两个版本的代码都适用于Python 2和Python 3,但第二个版本在使用Sphinx& amp;时为装饰函数创建了正确的原型。 autodoc(无需像this answer中所建议的那样在autodoc语句中重复原型。)
这是用cPython,我没有尝试过Jython等。