Python调试器中的列表推导作用域错误

71 投票
3 回答
8383 浏览
提问于 2025-04-18 09:20

在调试我的代码时,我想用列表推导式。不过,似乎在函数内部时,我无法在调试器中评估列表推导式。

我使用的是Python 3.4。

脚本内容:

$ cat test.py 
#!/usr/bin/python

def foo():
    x = [1, 2, 3, 3, 4]

    print(x)

foo()

交互式调试:

$ python3 -mpdb test.py                                                                                                                                           
> /tmp/test.py(3)<module>()
-> def foo():
(Pdb) step
> /tmp/test.py(8)<module>()
-> foo()
(Pdb) 
--Call--
> /tmp/test.py(3)foo()
-> def foo():
(Pdb) 
> /tmp/test.py(4)foo()
-> x = [1, 2, 3, 3, 4]
(Pdb) 
> /tmp/test.py(6)foo()
-> print(x)
(Pdb) p [x for _ in range(1)]
*** NameError: name 'x' is not defined
(Pdb) p x
[1, 2, 3, 3, 4]

为什么在列表推导式中,x 是未知的?我该如何在调试器中评估列表推导式,或者实现类似的功能?这是一个bug,还是调试器的某种基本限制?

3 个回答

11

在调试器中运行依赖外部环境的列表推导式

我发现大家推荐的解决办法对我没用。可能是因为我的设置和问题中的设置有些不同,我自己也分不清楚。但当我在网上搜索如何运行依赖于外部对象的列表推导式时,找到了这个地方。所以,我想把这个解决办法分享给其他有同样问题的人。在调试器中运行以下代码。

globals().update(locals())

这样,你的列表推导式就应该能顺利运行了。 来源。

14

pdb 似乎是在用以下方式运行代码:

eval(compiled_code, globals(), locals())

(或者可能只是用 eval(string, globals(), locals()) 这条命令)。

不幸的是,在编译时,Python 并不知道局部变量。通常这没什么大不了:

import dis
dis.dis(compile("x", "", "eval"))
#>>>   1           0 LOAD_NAME                0 (x)
#>>>               3 RETURN_VALUE

但是当引入了其他作用域,比如用列表推导式或 lambda 时,这就会编译得不好:

dis.dis(compile("(lambda: x)()", "", "eval"))
#>>>   1           0 LOAD_CONST               0 (<code object <lambda> at 0x7fac20708d20, file "", line 1>)
#>>>               3 LOAD_CONST               1 ('<lambda>')
#>>>               6 MAKE_FUNCTION            0
#>>>               9 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
#>>>              12 RETURN_VALUE
# The code of the internal lambda
dis.dis(compile("(lambda: x)()", "", "eval").co_consts[0])
#>>>   1           0 LOAD_GLOBAL              0 (x)
#>>>               3 RETURN_VALUE

注意这里是一个 LOAD_GLOBAL,而 x 是在局部作用域中的。


这里有一个非常简单的解决办法:

(Pdb) eval("(lambda: x)()", vars())
[1, 2, 3, 3, 4]
102

在Python 3中,由于对列表推导式的实现方式进行了更改,你必须在使用pdb调试器之前,先用interact命令,这样才能访问任何非全局变量。

>>> def foo(): [][0]
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in foo
IndexError: list index out of range
>>> import pdb;pdb.pm()
> <stdin>(1)foo()
(Pdb) x = 4
(Pdb) [x for _ in range(2)]
*** NameError: name 'x' is not defined
(Pdb) interact
*interactive*
>>> [x for _ in range(2)]
[4, 4]
>>> 

撰写回答