Подтвердить что ты не робот

Интроспекция Python: имя функции доступа и определение внутренней функции docstring

Рассмотрим следующий код python:

def function():
    "Docstring"

    name = ???
    doc = ???

    return name, doc

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

Что мне нужно, чтобы заменить вопросительные знаки, чтобы получить имя и docstring функции из одной и той же функции?

EDIT: Большинство ответов до сих пор явно жестко задают имя функции внутри ее определения. Возможно ли сделать что-то вроде ниже, где новая функция get_name_doc будет обращаться к функции из внешнего фрейма, из которой она вызвана, и вернуть ее имя и документ?

def get_name_doc():
    ???

def function():
    "Docstring"

    name, doc = get_name_doc()

    return name, doc

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

Ответ 1

Это невозможно сделать чисто последовательным образом, поскольку имена могут быть изменены и переназначены.

Однако вы можете использовать это, пока функция не переименована или не украшена.

>>> 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 не определено во время создания функции и является глобальной ссылкой в ​​функции. Таким образом, он получает доступ к глобальному охвату при каждом выполнении. Таким образом, изменение имени в глобальной области (например, декораторы) нарушит ваш код.

Ответ 2

В приведенном ниже коде решается проблема имени функции. Тем не менее, он не может определить правильную строку документа для примера, заданного aaronasterling. Интересно, есть ли способ вернуться к абстрактному дереву синтаксиса, связанному с объектом байт-кода. Тогда было бы довольно легко прочитать докшрин.

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')

Ответ 3

Это найдет имя и документ функции, вызывающей 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()

Ответ 4

Как насчет этого:

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 используется, чтобы дать decor свойства (имя, docstring и т.д.) исходной функции myfunc.

Ответ 5

>>> 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')

Ответ 6

Для моих личных проектов я разработал имя функции и doc методы восстановления функций и методов класса. Они реализованы в импортируемом модуле (SelfDoc.py), который имеет свой собственный тест в своем основном. Он включен ниже. Этот код выполняется как в Python 2.7.8 на Linux и MacOS. Он активно используется.

#!/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()

Ответ 7

Вы должны использовать имя функции для ее получения:

def function():
    "Docstring"

    name = function.__name__
    doc = function.__doc__

    return name, doc

Существует также модуль, называемый проверкой: http://docs.python.org/library/inspect.html. Это полезно для получения дополнительной информации о функции (или любом объекте python).

Ответ 8

def function():
    "Docstring"

    name = function.__name__
    doc = function.__doc__

    return name, doc    

Это должно сделать это, используйте имя функции в вашем случае function.

Вот очень хороший учебник, который рассказывает об этом: http://epydoc.sourceforge.net/docstrings.html

И, конечно же: http://docs.python.org/tutorial/controlflow.html#documentation-strings

Изменить: Обратитесь к вашей отредактированной версии вопроса, я думаю, вам может понадобиться с inspect.stack() из этого вопроса SO, Ответ Ayman Hourieh дает небольшой пример.

Ответ 9

>>> def function():
        "Docstring"

        name = function.__name__
        doc = function.__doc__

        return name, doc

>>> function()
('function', 'Docstring')

Ответ 10

для жестко запрограммированной версии, которая работает прилично с "хорошими" декораторами. Он должен быть объявлен после функции. если функция снова отскакивает, обновленные здесь изменения.

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

Это довольно неприятный взлом, поскольку он злоупотребляет тем, как работают аргументы по умолчанию. Он будет использовать любую функцию, связанную с тем, когда эта функция будет "инициализирована", и запомните ее, даже если функция будет восстановлена. Вызов его с помощью args приведет к интересным результатам.

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, а не с традиционным кодом, но он работает очень аккуратно. Кажется, это самый перспективный вариант. Возможно, вы сможете сделать это и с метаклассатом, что приводит к аккуратным кодам, а скорее к неприятным вещам, скрытым за кулисами, которые вам нужно закодировать. Этот идентификатор не рекомендуется

Я подозреваю, что, вероятно, есть более простой путь, чем переход на проверку/отражение/шаблоны/метаклассы, просто добавив следующую строку в конец модуля.

help(<module>)

где имя модуля, над которым вы работаете (строка). Или даже переменная __name__. Это можно сделать и в файле __init__.py, если вы работаете с несколькими модулями или на отдельных классах, я тоже думаю.

Ответ 11

Как отмечалось много раз, использование имени функции внутри функции фактически является динамическим поиском в 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__

Во многих случаях этого динамического поиска будет достаточно. Но на самом деле вам нужно повторно ввести имя функции внутри функции. Однако, если вы напишите вспомогательную функцию, чтобы узнать строку doc вызывающего, вы столкнетесь с тем фактом, что вспомогательная функция может жить в другом модуле с другим глобалом(). Таким образом, единственным правильным способом было бы использовать текущую информацию о кадре для поиска функции, но объект фрейма Python не имеет ссылки на объект функции, он содержит только ссылку на код "f_code", который он использует. Чтобы найти сопоставление от f_code к объекту функции, необходимо выполнить поиск по указанному "f_globals", например, следующим образом:

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_caller_doc() вместо get_my_doc(), потому что в подавляющем большинстве случаев вы хотите, чтобы строка doc передала ее в качестве аргумента некоторой вспомогательной функции. Но вспомогательная функция может легко получить строку doc от ее вызывающего абонента - я использую это в своих unittest скриптах, где вспомогательная функция может использовать строку 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)

Читателю предлагается расширить пример для других случаев использования.

Ответ 12

Ссылка http://stefaanlippens.net/python_inspect

import inspect
# functions
def whoami():
    return inspect.stack()[1][3]
def whocalledme():
    return inspect.stack()[2][3]
def foo():
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme())
    bar()
def bar():
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme())
johny = bar
# call them!
foo()
bar()
johny()

Вывод:

hello, I'm foo, daddy is ?
hello, I'm bar, daddy is foo
hello, I'm bar, daddy is ?
hello, I'm bar, daddy is ?

Ответ 13

Вы можете попробовать это:

import inspect


def my_function():
    """
    Hello World!
    """
    print(eval(inspect.currentframe().f_code.co_name).__doc__)


my_function()  # Hello World!