有效的方式来确定特定功能是否在Python堆栈上(Efficient way to determin

2019-07-17 14:23发布

为了调试,它往往是有用的告诉我们,如果一个特定函数的调用堆栈上涨。 例如,我们常常只希望运行调试代码时,某些功能打电话给我们。

一个解决方案是检查所有堆栈条目上涨的,但它这是一个函数,它是在栈深及多次呼吁,这会导致过多的开销。 现在的问题是要找到一个方法,使我们能够确定某一特定功能的方式,是相当有效的调用堆栈上的上涨。

类似

  • 获得的引用的功能从帧对象在执行堆栈上的对象? - 这个问题的重点是获得相关的函数对象,而不是确定,如果我们是在一个特定的功能。 虽然可以应用相同的技术,他们最终可能是非常低效的。

Answer 1:

除非你瞄准确实功能很特别的东西,以纪念“我的一个实例是在栈上的活动”(IOW:如果函数是质朴的,摸不着,也不可能进行了解你的这种特殊需要) ,没有想到的替代按帧的堆栈帧走,直到你打,无论上边(和功能是不存在的),或者你感兴趣的功能堆栈帧。 正如一些评论的问题表示,这是非常令人怀疑是否值得努力优化此。 但是,假设的说法,这值得的缘故...:

编辑 :原答案(由OP)有很多缺陷,但也有一些被修复,所以我编辑,以反映当前的情况以及为什么某些方面是很重要的。

首先,这是至关重要的使用try / except ,或with在装饰,使被监控从功能的出口是否正确地占了,不只是正常的人(如OP自己回答的原始版本一样)。

其次,每一个装饰应确保它保持了装饰功能的__name____doc__完好-这就是functools.wraps是(还有其他的方式,但wraps使得它简单)。

第三,正如第一点作为关键,一set ,这原本是由OP选择的数据结构,是错误的选择:一个函数可以在堆栈中多次(直接或间接递归)。 我们显然需要一个“多套”(也称为“包”),一组状结构,其跟踪的“多少次”每个项目存在。 在Python,自然实现一个多重的是作为一个字典映射按键数量,而这又是一个最轻易实现collections.defaultdict(int)

四,一般的做法应该是线程安全的(时可以很容易地实现,至少;-)。 幸运的是, threading.local变得比较繁琐,如果适用-在这里,应该肯定是(有电话了自己独立的线程每个堆栈)。

第五,已经在一些评论(注意到在一些答案所提供的装修多么严重与其他装饰起到了开始讨论一个有趣的问题:监控装饰似乎已经是最后的(最)一个,否则检查打破了这种从何而来。使用函数对象本身作为重点纳入监测字典的自然,但不幸的选择。

我建议由不同的选择键来解决这个问题:让装饰花(串,说的) 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

我没有测试此代码,因此它可能包含一些错字或类似的,但我提供它,因为我希望它覆盖所有重要的技术点我上面所解释的。

它是一切值得吗? 或许不会,如前所述。 不过,我认为沿着线“如果它是值得做的话,那么它的价值做对” ;-)。



Answer 2:

我真的不喜欢这种方法,但这里是你在做什么固定的后续版本:

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()

这种处理递归,线程和异常。

我不喜欢这种方法的原因有两个:

  • 如果函数进一步装饰这是行不通的:这一定是最后的装饰。
  • 如果你想利用这个进行调试,这意味着你必须在两个地方用它来编辑代码; 一个添加的装饰,以及一个使用它。 它更方便,只需检查堆栈,所以你只需要在你调试的代码编辑代码。

更好的方法是直接检查堆栈(可能为原生扩展速度),如果可能的话,找到一种方法来缓存堆栈帧的寿命的效果。 (我不知道这是可能的,而无需修改Python的核心,虽然)。



文章来源: Efficient way to determine whether a particular function is on the stack in Python