除非您要使用的功能做了非常特殊的标记以标记“我的一个实例在堆栈中处于活动状态”(爱荷华州:如果该功能是原始且不可触摸的,并且无法意识到您的这种特殊需求)
,没有办法想到逐帧向上堆栈,直到您击中顶部(且该功能不存在)或您感兴趣的功能的堆栈框架为止。正如对该问题的几条评论所表明的那样,是否值得努力优化这一点非常令人怀疑。但是,为了争论起见,认为这
是 值得的…:
编辑 :(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 collectionsimport functoolsimport threadingthreadlocal = 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 vdef 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
我没有测试过此代码,因此它可能包含一些错字或类似内容,但我提供它是因为我希望它确实涵盖了我上面解释的所有重要技术要点。
值得吗?如前所述,可能不是。但是,我认为,“如果值得一做,那就值得做正确” ;-)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)