20

次の Python コードを検討してください。

def function():
    "Docstring"

    name = ???
    doc = ???

    return name, doc

>>> function()
"function", "Docstring"

同じ関数内から関数の名前とドキュメント文字列を取得するには、疑問符を何に置き換える必要がありますか?

編集:これまでの回答のほとんどは、定義内の関数の名前を明示的にハードコーディングしています。新しい関数 get_name_doc が呼び出された外側のフレームから関数にアクセスし、その名前とドキュメントを返す場合、以下のようなことを行うことは可能ですか?

def get_name_doc():
    ???

def function():
    "Docstring"

    name, doc = get_name_doc()

    return name, doc

>>> function()
"function", "Docstring"
4

13 に答える 13

17

名前は変更して再割り当てできるため、これを一貫した方法できれいに行うことはできません。

ただし、関数の名前を変更したり装飾したりしない限り、これを使用できます。

>>> def test():
...     """test"""
...     doc = test.__doc__
...     name = test.__name__
...     return doc, name
... 
>>> test()
('test', 'test')
>>> 

それはまったく信頼できません。これがうまくいかない例です。

>>> def dec(f):
...     def wrap():
...         """wrap"""
...         return f()
...     return wrap
... 
>>> @dec
... def test():
...     """test"""
...     return test.__name__, test.__doc__
... 
>>> test()
('wrap', 'wrap')
>>> 

これはtest、関数が実際に作成された時点では名前が定義されておらず、関数内のグローバル参照であるためです。したがって、実行のたびにグローバルスコープで検索されます。そのため、グローバル スコープ (デコレータなど) で名前を変更すると、コードが壊れます。

于 2012-04-06T00:36:21.713 に答える
8

以下のコードは、関数の名前の問題を解決します。ただし、aaronasterling によって提供された例では、正しい docstring を検出できません。バイトコード オブジェクトに関連付けられた抽象構文ツリーに戻る方法はあるのでしょうか。そうすれば、docstring を読むのは非常に簡単になります。

import inspect

def get_name_doc():
    outerframe = inspect.currentframe().f_back
    name = outerframe.f_code.co_name
    doc = outerframe.f_back.f_globals[name].__doc__    
    return name, doc

if __name__ == "__main__":

    def function():
        "Docstring"

        name, doc = get_name_doc()

        return name, doc

    def dec(f):
        def wrap():
           """wrap"""
           return f()
        return wrap

    @dec
    def test():
        """test"""
        return get_name_doc()

    assert function() == ('function', "Docstring")
    #The assertion below fails:. It gives: ('test', 'wrap')
    #assert test() == ('test', 'test')
于 2012-04-06T01:38:49.837 に答える
3

これはどう:

import functools

def giveme(func):
    @functools.wraps(func)
    def decor(*args, **kwargs):
        return func(decor, *args, **kwargs)
    return decor

@giveme
def myfunc(me):
    "docstr"
    return (me.__name__, me.__doc__)

# prints ('myfunc', 'docstr')
print myfunc()

間もなく、givemeデコレータは(装飾された)関数オブジェクトを最初の引数として追加します。このようにして、関数は呼び出されたときに自身の名前とdocstringにアクセスできます。

装飾のため、元のmyfunc関数はに置き換えられdecorます。最初の引数をとまったく同じにするためmyfuncに、関数に渡されるのはであり、ではdecorありませんfunc

functools.wrapsデコレータは、元の関数のプロパティ(name、docstringなど)を与えるために使用されdecorますmyfunc

于 2012-04-06T03:59:19.360 に答える
2

これにより、get_doc を呼び出す関数の名前とドキュメントが検索されます。私の感覚では、 get_doc は関数を引数として持つべきです (そうすれば本当に簡単になりますが、達成するのは楽しくありません;))

import inspect

def get_doc():
    """ other doc
    """
    frame = inspect.currentframe()

    caller_frame = inspect.getouterframes(frame)[1][0]
    caller_name = inspect.getframeinfo(caller_frame).function
    caller_func = eval(caller_name)

    return caller_name, caller_func.__doc__


def func():
    """ doc string """
    print get_doc()
    pass


def foo():
    """ doc string v2 """
    func()

def bar():
    """ new caller """
    print get_doc()

func()
foo()
bar()
于 2012-04-06T01:03:32.243 に答える
2

個人的なプロジェクトでは、関数とクラス メソッドの関数名とドキュメントの回復手法を開発しました。これらは、 mainに独自の自己テストを持つインポート可能なモジュール (SelfDoc.py) に実装されています。以下に含まれています。このコードは、Linux および MacOS で Python 2.7.8 と同じように実行されます。活躍中です。

#!/usr/bin/env python

from inspect import (getframeinfo, currentframe, getouterframes)

class classSelfDoc(object):

    @property
    def frameName(self):
        frame = getframeinfo(currentframe().f_back)
        return str(frame.function)

    @property
    def frameDoc(self):
        frame = getframeinfo(currentframe().f_back)
        doc = eval('self.'+str(frame.function)+'.__doc__')
        return doc if doc else 'undocumented'

def frameName():
    return str(getframeinfo(currentframe().f_back).function)

def frameDoc():
    doc = eval(getframeinfo(currentframe().f_back).function).__doc__
    return doc if doc else 'undocumented'

if __name__ == "__main__":

    class aClass(classSelfDoc):
        "class documentation"

        def __init__(self):
            "ctor documentation"
            print self.frameName, self.frameDoc

        def __call__(self):
            "ftor documentation"
            print self.frameName, self.frameDoc

        def undocumented(self):
            print self.frameName, self.frameDoc

    def aDocumentedFunction():
        "function documentation"
        print frameName(), frameDoc()

    def anUndocumentedFunction():
        print frameName(), frameDoc()

    anInstance = aClass()
    anInstance()
    anInstance.undocumented()

    aDocumentedFunction()
    anUndocumentedFunction()
于 2014-10-04T15:43:32.183 に答える
2
>>> import inspect
>>> def f():
...     """doc"""
...     name = inspect.getframeinfo(inspect.currentframe()).function
...     doc = eval(name + '.__doc__')
...     return name, doc
... 
>>> f()
('f', 'doc')
>>> class C:
...     def f(self):
...         """doc"""
...         name = inspect.getframeinfo(inspect.currentframe()).function
...         doc = eval(name + '.__doc__')
...         return name, doc
... 
>>> C().f()
('f', 'doc')
于 2013-10-25T21:45:32.490 に答える
1
>>> def function():
        "Docstring"

        name = function.__name__
        doc = function.__doc__

        return name, doc

>>> function()
('function', 'Docstring')
于 2012-04-06T00:36:30.213 に答える
1

何度も指摘されているように、関数内で関数名を使用することは、実際には現在のモジュールの globals() での動的ルックアップです。名前解決は globals() 辞書で再び機能するため、あらゆる種類の eval() を使用することは、それのバリエーションにすぎません。ほとんどの例はメンバー関数で失敗します。最初に globals() からクラス名を検索する必要があり、それからメンバー関数にアクセスできます。だから実際に

def function():
    """ foo """
     doc = function.__doc__

class Class:
    def function():
        """ bar """
        doc = Class.function.__doc__

と同等です

def function():
    """ foo """
     doc = globals()["function"].__doc__

class Class:
    def function():
        """ bar """
        doc = globals()["Class"].function.__doc__

多くの場合、この動的ルックアップで十分です。しかし実際には、関数内で関数名を再入力する必要があります。ただし、ヘルパー関数を作成して呼び出し元のドキュメント文字列を見つけると、ヘルパー関数が異なる globals() ディクショナリを持つ別のモジュールに存在する可能性があるという事実に直面します。したがって、唯一の正しい方法は、現在のフレーム情報を使用して関数を見つけることです。ただし、Python のフレーム オブジェクトには関数オブジェクトへの参照がなく、使用する「f_code」コードへの参照のみが含まれます。参照された「f_globals」ディクショナリを検索して、f_code から関数オブジェクトへのマッピングを見つける必要があります。たとえば、次のようになります。

import inspect

def get_caller_doc():
    frame = inspect.currentframe().f_back.f_back
    for objref in frame.f_globals.values():
        if inspect.isfunction(objref):
            if objref.func_code == frame.f_code:
                return objref.__doc__
        elif inspect.isclass(objref):
            for name, member in inspect.getmembers(objref):
                if inspect.ismethod(member):
                    if member.im_func.func_code == frame.f_code:
                        return member.__doc__

get_my_doc() ではなく get_caller_doc() という名前が付けられているのは、ほとんどの場合、ドキュメント文字列をヘルパー関数の引数として渡す必要があるためです。しかし、ヘルパー関数は呼び出し元からドキュメント文字列を簡単に取得できます。ヘルパー関数がテストのドキュメント文字列を使用してログに投稿したり、実際のテスト データとして使用したりできるユニットテスト スクリプトでこれを使用しています。そのため、提示されたヘルパーはテスト関数とテスト メンバー関数のドキュメント文字列のみを検索します。

class MyTest:
    def test_101(self):
        """ some example test """
        self.createProject("A")
    def createProject(self, name):
        description = get_caller_doc()
        self.server.createProject(name, description)

他の使用例のために例を拡張することは、読者に任されています。

于 2014-08-28T13:28:53.837 に答える
1

「行儀の良い」デコレータで適切に機能するハードコードされたバージョンの場合。関数の後に宣言する必要があります。関数が後でリバウンドした場合、変更はここで更新されます。

def get_name_doc():
    # global function # this is optional but makes your intent a bit more clear.
    return function.__name__, function.__doc__

これは、デフォルトの引数の動作を悪用するという点で、かなり厄介なハックです。この関数が「初期化」された時点でバインドされている関数を使用し、関数が再バインドされた場合でもそれを記憶します。引数を指定して呼び出すと、興味深い結果が得られます。

def get_name_doc(fn=function):
    return fn.__name__, fn.__doc__

動的なものはまだハードコーディングされていますが、True の引数で関数が呼び出されると更新されます。基本的に、このバージョンは指示された場合にのみ更新されます。

def get_name_doc(update=False):
    global fn
    if update:
        fn = function
    return fn.__name__, fn.__doc__

もちろん、これにもデコレータの例があります。

@decorator  # applying the decorator decorator to make it well behaved
def print_name_doc(fn, *args, **kwargs):
    def inner(*args, **kwargs):
         print(fn.__doc__, fn.__name__)  # im assuming you just want to print in this case
         return fn(*args, **kwargs)
return inner

デコレータ デコレータ (少なくとも) を読む必要があります。ハードコーディングされていることに注意する必要があるため、(コレクションモジュールからの)NamedTupleソースを見てください。悲しいことに、名前付きタプルのコードはかなり奇妙です。これは、従来のコードではなく eval で使用される文字列形式ですが、非常にうまく機能します。これは最も有望なバリアントのようです。メタクラスでもこれを実行できる可能性があります。これにより、きれいなコードが得られますが、コーディングが必要な舞台裏に隠された厄介なものが発生します。このIDは反対することをお勧めします

モジュールの最後に次の行を追加するだけで、インスペクション/リフレクション/テンプレート/メタクラスに入るよりも簡単な方法があると思います。

help(<module>)

ここで、作業中のモジュールの名前 (文字列) です。または、変数 __name__ でさえ。複数のモジュールまたは個々のクラスで作業している場合、これは __init__.py ファイルでも実行できます

于 2014-04-29T18:20:20.380 に答える