题
调试,它是非常有用告诉我们,如果一个特定功能是更高的话堆。例如,我们经常只要运行调试的代码一定时功能称为我们的。
一种解决办法是审查所有的堆项更高,但这是在一个函数,是在深叠和重复所谓的,这导致过多的开销。问题是找到方法,使我们能够确定,如果一个特定功能是更高的话栈的方式是合理的效率。
类似的
- 获得提到功能上的对象执行栈从框架的对象? -这个问题的重点是获得的功能物体,而不是确定如果我们是在一个特定的功能。虽然同样的技术可应用,它们可能最终会效率极低。
解决方案
除非本功能你瞄准的目标做了什么非常特殊的标记"的一个实例,我是活跃在该堆"(IOW:如果功能是原始的和不可接触的和不可能是意识到这个特殊的需要你的),也没有可以想象的替代行帧一帧地上堆直到你打的顶部(和功能是不存在)或是一堆框架的功能利益。作为若干评论意见的问题时表示,这是极其令人怀疑是否值得努力,以优化这个。但是,假设为的论点,即它 是 值得...:
编辑:原来回答(由OP)有许多缺陷,但是一些已经固定的,因此我的编辑,以反映目前的情况以及为什么某些方面是重要的。
首先,它是至关重要的使用 try
/except
, 或 with
, 在装饰的,因此,任何退出,从一个功能正在监测是正确入账,不仅仅是正常的(作为原始版本的运自己的答案没有).
第二,每一个装饰应当确保它保持功能的装饰的 __name__
和 __doc__
完整-这是什么 functools.wraps
是(还有其他的方法,但 wraps
让它简单的).
第三,正如至关重要的,因为第一点, set
, 这是数据结构最初选择的运,是错误的选择:功能可以在堆的若干次(直接或间接递归).我们显然需要一个"多设"(也称为"袋"),一组结构,跟踪"多少次"每个项目的存在。在蟒蛇,自然执行的一个多集是作为一个字典映键计数,而这又是最轻而易举地实现为 collections.defaultdict(int)
.
第四,一般的做法应线程安全(当可以完成很容易,至少;-).幸运的是, threading.local
使得微不足道的,当适用--并在此,应当是(每组具有其自己独立的线呼叫)。
第五,一个有趣的问题,已经提出了一些评论意见(注意到多么严重,所提供的装饰在一些答案,发挥与其他装饰:监视装饰似乎是最后的(外)一,否则检查的休息时间。这是来自自然的,但不幸选择使用功能物体本身作为关键监督词典。
我提议来解决这一不同的选择的关键:让的装饰采取一(string,说) identifier
参数,必须是唯一的(在每个给线)和使用的标识符作为关键监督词典。代码检查堆当然必须意识到的标识符和使用它。
在装饰的时间,装饰可以检查的独特性(通过使用一个单独的组)。该标识符,也可以留到默认的名称的功能(那么这是唯一明确需要保持灵活性,监测同名的功能在同一个名字空间);唯一财产可以明确放弃当几个监测职能都被认为"相同"用于监测目的(这种可能的情况下,如果给定 def
声明的目的是要执行的多次稍有不同的上下文中作出若干功能物体的程序要考虑"同样的功能"用于监测目的)。最后,应该尽可能有选择地恢复到的"功能物体标识符作为"对于那些罕见的情况下在其中进一步装饰被称为是不可能的(因为在这些情况下,它可以最方便的方式来保证独特性).
因此,把这些多方面的考虑在一起,我们可以有(包括一个 threadlocal_var
实用功能,将有可能已经在一个工具箱模块的课程;-)类似于以下...:
import collections
import functools
import threading
threadlocal = threading.local()
def threadlocal_var(varname, factory, *a, **k):
v = getattr(threadlocal, varname, None)
if v is None:
v = factory(*a, **k)
setattr(threadlocal, varname, v)
return v
def monitoring(identifier=None, unique=True, use_function=False):
def inner(f):
assert (not use_function) or (identifier is None)
if identifier is None:
if use_function:
identifier = f
else:
identifier = f.__name__
if unique:
monitored = threadlocal_var('uniques', set)
if identifier in monitored:
raise ValueError('Duplicate monitoring identifier %r' % identifier)
monitored.add(identifier)
counts = threadlocal_var('counts', collections.defaultdict, int)
@functools.wraps(f)
def wrapper(*a, **k):
counts[identifier] += 1
try:
return f(*a, **k)
finally:
counts[identifier] -= 1
return wrapper
return inner
我没有测试过这样的代码,因此可能含有一些打印错误或类似的,但是我提供它,因为我希望它不会涵盖的所有重要技术要点,我上面所解释的。
是这一切值得吗?可能不会,因为先前解释。然而,我认为沿线的"如果这是值得做,这是值得做的正确的";-).
其他提示
我真的不喜欢这种方法,但这里的一个固定的版本的你在做什么:
from collections import defaultdict
import threading
functions_on_stack = threading.local()
def record_function_on_stack(f):
def wrapped(*args, **kwargs):
if not getattr(functions_on_stack, "stacks", None):
functions_on_stack.stacks = defaultdict(int)
functions_on_stack.stacks[wrapped] += 1
try:
result = f(*args, **kwargs)
finally:
functions_on_stack.stacks[wrapped] -= 1
if functions_on_stack.stacks[wrapped] == 0:
del functions_on_stack.stacks[wrapped]
return result
wrapped.orig_func = f
return wrapped
def function_is_on_stack(f):
return f in functions_on_stack.stacks
def nested():
if function_is_on_stack(test):
print "nested"
@record_function_on_stack
def test():
nested()
test()
这把手递归线和例外情况。
我不喜欢这种方法有两个原因:
- 它不工作,如果该职能是装饰进一步:这必须是最后的装饰。
- 如果你使用这种调试,这意味着你必须编码在两个地方使用;一添加的装饰,和一个使用它。它更方便只是检查叠,所以你只需要编码代码你在调试。
一个更好的办法是审查堆直接(可能为本地的扩展速度),并且如果可能的话,找到一种方法来缓的结果的使用寿命堆的框架。(我不知道如果这是可能的,而无需修改蟒蛇的核心,虽然。)