13

ミラーリングされたクライアントとサーバーAPIを含む、作業中のオープンソースプロジェクトのためにドキュメントを取得しようとしています。この目的のために、入力に対して検証を実行するだけのメソッドを文書化するためにほとんどの場合に使用できるデコレータを作成しました。これらのメソッドでいっぱいのクラスはここにあり、デコレータの実装はここにあります

ご覧のとおり、デコレータfunctools.wrapsはdocstringを保持するために使用され、署名も考えましたが、ソースコードと生成されたドキュメントは次のようになります。

ソース:ソースコード

vs

ドキュメント:スフィンクスのドキュメント

setH生成されたドキュメントに正しい呼び出し署名を表示させる方法を知っている人はいますか?(署名ごとに新しいデコレータがなくても、ミラーリングする必要のあるメソッドが何百もあります)

デコレータがバインドされていないメソッドを変更しないが、クラスがバインド時にメソッドを変更する(オブジェクトのインスタンス化)ことを含む回避策を見つけました-これはハックのようですが、これに関するコメント、または別の方法これ、いただければ幸いです。

4

4 に答える 4

4

標準ライブラリの外にあるあまりにもマックに依存することを避けたいので、デコレータモジュールを見ている間、私は主にその機能を再現しようとしました....失敗しました...

そこで、別の角度から問題を調べました。これで、部分的に機能する解決策が得られました。これは、主にこのコミットを見るだけで説明できます。部分的な使用に依存しているため、完全ではありません。これは、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

したがって、これは実際にはバインドされていないメソッドを変更しません。生成されたドキュメントは同じままです。トリックの2番目の部分は、クラスの初期化時に発生します。

 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属性を壊してしまうのではないかと心配しています...

ですから、私自身の質問に対する答えはありますが、私はそれに完全に満足していません。


アップデート

さて、もう少し作業をした後、私はこれに行き着きました:

 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属性は使用されていなくてもアクセスされるため、存在しない場合はオブジェクト属性は変更されません。

誰かがこれについて何かコメントがあれば私は興味がありますか?

于 2013-01-22T11:20:18.790 に答える
4

PRAWでは、スフィンクスビルドが発生しているときに(装飾された関数ではなく)元の関数を返す条件付きデコレータを使用して、この問題を処理しました。

PRAWのsphinxconf.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が正しい署名を取得できるようにするものです。

関連する情報源

于 2014-02-25T19:07:31.527 に答える
3

functools.wraps__name__、、、__doc__およびのみを保持し__module__ます。署名を保持するために、MicheleSimionatoのDecoratorモジュールも見てください。

于 2013-01-21T22:34:52.737 に答える
2

イーサンの答えに対する私の短いコメントを拡張するために、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を決定したかったのです。したがって、両方のバージョンで、ネストされたラッパー関数を使用したアプローチ。

コードの両方のバージョンはPython2とPython3で動作しますが、2番目のバージョンはSphinxとautodocを使用するときに装飾された関数の正しいプロトタイプを作成します(この回答で提案されているように、autodocステートメントでプロトタイプを繰り返す必要はありません)。

これはcPythonの場合で、Jythonなどは試していません。

于 2016-08-29T15:20:51.443 に答える