Я могу пойти лучше, чем @jme. Вот версия его декоратора, что отступы и dedents согласно вашему положению в стеке вызовов:
import functools
# a factory for decorators
def create_tracer(tab_width):
indentation_level = 0
def decorator(f): # a decorator is a function which takes a function and returns a function
@functools.wraps(f)
def wrapper(*args): # we wish to extend the function that was passed to the decorator, so we define a wrapper function to return
nonlocal indentation_level # python 3 only, sorry
msg = " " * indentation_level + "{}({})".format(f.__name__, ", ".join([str(a) for a in args]))
print(msg)
indentation_level += tab_width # mutate the closure so the next function that is called gets a deeper indentation level
result = f(*args)
indentation_level -= tab_width
return result
return wrapper
return decorator
tracer = create_tracer(4) # create the decorator itself
@tracer
def f1():
x = f2(5)
return f3(x)
@tracer
def f2(x):
return f3(2)*x
@tracer
def f3(x):
return 4*x
f1()
Выход:
f1()
f2(5)
f3(2)
f3(40)
nonlocal
заявление позволяет мутировать indentation_level
во внешней области. При входе в функцию мы увеличиваем уровень отступа, чтобы следующий print
получил отступы дальше. Затем, после выхода, мы снова уменьшаем его.
Это называется decorator syntax. Это чисто «синтаксический сахар»; преобразование в эквивалентный код без @
очень прост.
@d
def f():
pass
это так же, как:
def f():
pass
f = d(f)
Как вы можете видеть, @
просто использует декоратор для обработки декорированной функции в некотором роде, и заменяет исходную функцию с результатом, так же, как в ответе @ jme. Это как Invasion of the Body Snatchers; мы заменяем f
на что-то похожее на f
, но ведет себя по-другому.
Если вы застряли на Python 2, вы можете имитировать nonlocal
заявление, используя класс с переменной экземпляра. Это может сделать для вас немного больше смысла, если вы никогда раньше не использовали декораторов.
# a class which acts like a decorator
class Tracer(object):
def __init__(self, tab_width):
self.tab_width = tab_width
self.indentation_level = 0
# make the class act like a function (which takes a function and returns a function)
def __call__(self, f):
@functools.wraps(f)
def wrapper(*args):
msg = " " * self.indentation_level + "{}({})".format(f.__name__, ", ".join([str(a) for a in args]))
print msg
self.indentation_level += self.tab_width
result = f(*args)
self.indentation_level -= self.tab_width
return result
return wrapper
tracer = Tracer(4)
@tracer
def f1():
# etc, as above
Вы сказали, что вы не можете изменить существующие функции.Вы можете ретро-подходят декоратор по возни с globals()
(хотя это обычно не является хорошей идеей, если вы действительно необходимости делать это):
for name, val in globals().items(): # use iteritems() in Python 2
if name.contains('f'): # look for the functions we wish to trace
wrapped_func = tracer(val)
globals()[name] = wrapped_func # overwrite the function with our wrapped version
Если у вас нет доступа к источнику данного модуля вы можете добиться чего-то очень похожего, проверив импортированный модуль и изменив экспортируемые им элементы.
Небо предел с этим подходом. Вы могли бы построить это в инструменте анализа кода промышленного уровня, сохранив вызовы в некотором виде структуры данных графа, а не просто отступы и печать. Затем вы можете запросить свои данные, чтобы ответить на такие вопросы, как «какие функции в этом модуле называются наиболее?» или «какие функции являются самыми медленными?». На самом деле, это отличная идея для библиотеки ...
Не легко. Вероятно, вы могли бы это сделать с помощью встроенных модулей 'ast',' compiler' и 'inspect', используя' inspect.getsource (...) 'для извлечения исходного кода самого файла Python, а затем используя 'compiler.parse (...)', чтобы получить структуру данных [AST] (http://en.wikipedia.org/wiki/Abstract_syntax_tree), которую вы можете исследовать, чтобы выяснить, что называет. –
Присоединение к 'sys.settrace' и проверка объектов фрейма должны быть проще и точнее, чем любой статический анализ (что предполагает предложение Джереми Бэнкса). С другой стороны, для этого потребуется обильное количество черной магии, специфичной для CPython. – delnan
Я считаю, что есть более простой способ, чем статический анализ и встроенный отладчик. Это связано с тем, что моя задача представлена в конкретном случае, а не в общем случае. – Max