让狮身人面像识别正确的签名

时间:2013-01-17 16:38:56

标签: python decorator python-sphinx

我一直在尝试获取我的文档,以便我正在开发一个包含镜像客户端和服务器API的开源项目。为此,我创建了一个装饰器,大部分时间都可以用来记录一个简单地对其输入执行验证的方法。您可以找到一个包含这些方法的类here和装饰器的实现here

正如您所见,装饰器使用functools.wraps来保留文档字符串,我还想到了签名,但源代码与生成的文档看起来像这样:

资料来源:source code

VS

文档:sphinx docs

有没有人知道让setH生成的文档显示正确的呼叫签名的方法是什么? (没有为每个签名设置一个新的装饰器 - 我需要镜像的方法)

我找到了一个解决方法,其中涉及让装饰器不更改未绑定方法,但让类在绑定时改变方法(对象实例化) - 这看起来像是一个黑客,所以对此有任何意见,或替代这样做的方式,将不胜感激。

4 个答案:

答案 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函数。

所以这个问题很多:

  1. 在初始化时使用partial会导致丢失绑定的方法信息。
  2. 我担心碰撞属性恰好具有run属性......
  3. 因此,虽然我对自己的问题有了答案,但我对此并不满意。


    更新

    好的,经过多一点工作,我最终得到了这个:

     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选择正确的签名。

Relevant Source

答案 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等。