确定特定功能是否在Python堆栈中的有效方法
问题内容:
对于调试而言,通常可以判断某个特定函数是否在调用堆栈中更高。例如,当某个函数调用我们时,我们通常只想运行调试代码。
一种解决方案是检查所有更高级别的堆栈条目,但这是在堆栈深处并反复调用的函数中,这会导致过多的开销。问题是找到一种方法,使我们能够以合理有效的方式确定特定函数是否在调用堆栈中更高。
类似
- 从框架对象获取对执行堆栈上的功能对象的引用?-这个问题着重于获取函数对象,而不是确定我们是否在特定函数中。尽管可以应用相同的技术,但它们最终可能效率极低。
问题答案:
除非您要使用的功能做了非常特殊的标记以标记“我的一个实例在堆栈中处于活动状态”(爱荷华州:如果该功能是原始且不可触摸的,并且无法意识到您的这种特殊需求)
,没有办法想到逐帧向上堆栈,直到您击中顶部(且该功能不存在)或您感兴趣的功能的堆栈框架为止。正如对该问题的几条评论所表明的那样,是否值得努力优化这一点非常令人怀疑。但是,为了争论起见,认为这
是 值得的…:
编辑 :(OP的)原始答案有很多缺陷,但是自那以后已经修复了一些缺陷,因此,我正在编辑以反映当前情况以及某些方面为何重要的原因。
首先,在修饰器中使用try
/except
或至关重要with
,这样才能正确地考虑从被监视功能中退出的任何情况,而不仅仅是正常操作(就像OP自己答案的原始版本那样)。
其次,每一个装饰应确保它保持了装饰功能的__name__
和__doc__
完整的-
这就是functools.wraps
对(还有其他的方式,但wraps
使得它简单)。
第三,与第一点一样重要set
,它是OP最初选择的数据结构,它是错误的选择:一个函数可以在堆栈上多次(直接或间接递归)。我们显然需要一个“多套”(也称为“袋子”),这是一种类似套的结构,可以跟踪每个物品存在“多少次”。在Python中,多集的自然实现是将dict键映射到count的dict,而dict则最容易实现为collections.defaultdict(int)
。
第四,通用方法应该是线程安全的(至少可以轻松实现的;-)。幸运的是,threading.local
在适用的情况下,它变得微不足道了-
在这里,它肯定应该是(每个堆栈都有其自己单独的调用线程)。
第五,一个有趣的问题,已经在一些评论中提到(注意到某些答案中提供的装饰者与其他装饰者玩得多么糟糕:监视装饰者似乎必须是最后一个(最外面的)装饰者,否则检查会中断。这来自使用功能对象本身作为监视命令的键是自然而不幸的选择。
我建议通过不同的键选择来解决此问题:让装饰器采用identifier
(在每个给定线程中)必须唯一的(字符串)参数,并使用标识符作为监视dict的键。当然,检查堆栈的代码必须知道标识符并也要使用它。
在装饰时,装饰器可以检查uniqueness属性(通过使用单独的集合)。标识符可以保留为默认的函数名称(因此,只有在保持相同名称空间中监视同名函数的灵活性时才需要明确使用标识符);当出于监视目的将多个受监视功能视为“相同”时,可以显式放弃唯一性属性(如果给定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
我没有测试过此代码,因此它可能包含一些错字或类似内容,但我提供它是因为我希望它确实涵盖了我上面解释的所有重要技术要点。
值得吗?如前所述,可能不是。但是,我认为,“如果值得一做,那就值得做正确” ;-)。