Интроспекция 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"
Ответы
Ответ 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!