如何调试动态定义的Python函数?

4 投票
2 回答
3733 浏览
提问于 2025-04-15 17:40

有没有办法调试一个在运行时动态定义的函数呢?

或者至少有没有简单的方法可以找到这个函数是怎么产生的?

更新以提供更多细节:

我使用了 inspect 模块:

ipdb> inspect.getmodule(im.get_thumbnail_url)
Out[0]: <module 'django.utils.functional' from 'C:\java\python\Python25\Lib\site
-packages\django\utils\functional.pyc'>
ipdb> inspect.getsource(im.get_thumbnail_url)
Out[0]: '    def _curried(*moreargs, **morekwargs):\n        return _curried_fun
c(*(args+moreargs), **dict(kwargs, **morekwargs))\n'

这里 inspect 显示,photos.models.Image 类中的 get_thumbnail_url 方法是由 django.utils.functional.curry._curried 函数生成的。但它仍然没有显示这个方法是在哪里产生的,也就是 _curried 函数是在哪里被调用的。这些信息对于了解 get_thumbnail_url 是怎么实现的非常重要。

我可以在 _curried 函数里面放 pdb,但是这样会经常中断,因为这个函数调用非常频繁。我需要一些特别的特征来使用断点条件。

关于解决方案的更新:

感谢大家的建议。我找到了解决方案。让我解释一下我是怎么找到的,或许能帮助到其他人:

首先,我在 pinax 源代码中搜索了 'get_thumbnail_url' 这个词。没有结果。

然后,我在 pinax 源代码中搜索了 'thumbnail' 这个词。也没有有用的结果。

最后,我在 pinax 源代码中搜索了 'curry' 这个词。以下是几个结果之一:

def add_accessor_methods(self, *args, **kwargs):
    for size in PhotoSizeCache().sizes.keys():
        setattr(self, 'get_%s_size' % size,
                curry(self._get_SIZE_size, size=size))
        setattr(self, 'get_%s_photosize' % size,
                curry(self._get_SIZE_photosize, size=size))
        setattr(self, 'get_%s_url' % size,
                curry(self._get_SIZE_url, size=size))
        setattr(self, 'get_%s_filename' % size,
                curry(self._get_SIZE_filename, size=size))

get_thumbnail_url 方法是通过这个调用生成的:curry(self._get_SIZE_url, size=size))

但当然这不是一个通用的解决方法。如果你能分享其他找到动态定义函数实际产生位置的方法,那将非常有帮助。

编辑:

最佳的 通用解决方案 是由 Jason Orendorff 写的。

2 个回答

1

不太确定,不过你可以用一个叫做inspect的模块来获取一个函数的定义位置,包括它所在的文件和行号:

import inspect
f = lambda: inspect.currentframe().f_code.co_filename + ':' + \
  str(inspect.currentframe().f_lineno);
print f()

它会打印出:

script.py:2
1

给定一个函数 f,在 CPython 中,你可以通过打印 f.func_code.co_filenamef.func_code.co_firstlineno 来获取这个函数所在的文件和它的第一行行号。不过,如果这个函数是通过 evalexec 创建的,这个方法就没用了。

错误追踪信息中也包含文件和行号的信息。

如果你 import dis,你可以使用 dis.dis(f) 来查看 CPython 的字节码;这可能不是特别有用,但有可能显示一些字符串,帮助你找到正确的位置。

另一个可以查看的工具是 PDB,这是 Python 的文本模式调试器。在出现异常后,使用 import pdb; pdb.pm() 可以启动 PDB。虽然它比较原始,但还是很有用的;你可以输入 help 来查看命令列表。where 命令可以显示调用栈。

编辑:你提到这是一个柯里化函数。如果它是通过 functools.partial 创建的,那么它会有一个 .func 属性,指向底层的函数。

编辑 2:一般的方法是在 im.get_thumbnail_url 上设置一个断点,然后立即调用它。你的断点应该会立刻被触发。然后逐步执行,直到到达你感兴趣的代码。

因为这个柯里化函数是通过类似下面的代码生成的:

def curry(_curried_func,  *args,  **kwargs):
    def _curried(*moreargs, **morekwargs):
        return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
    return _curried

另一种方法是检查 f.func_closure,像这样:

>>> f
<function _curried at 0xb77d64fc>
>>> f.func_closure
(<cell at 0xb77eb44c: tuple object at 0xb77dfa0c>, <cell at 0xb77eb5e4: dict object at 0xb77d93e4>, <cell at 0xb77eb5cc: function object at 0xb77d1d84>)

这个函数闭包包含三个变量:一个元组、一个字典和一个函数。显然,我们感兴趣的是那个函数(顺便说一下,这三个单元对应于在外部函数 curry 中定义的变量 argskwargs_curried_func,而这些变量被闭包 _curried 使用)。

>>> f.func_closure[2].cell_contents
<function say at 0xb77d1d84>
>>> import inspect
>>> inspect.getsource(_)
'def say(x):\n    print x\n'

撰写回答