首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用列表生成器时,Python 3中的pdb模块中可能存在错误

使用列表生成器时,Python 3中的pdb模块中可能存在错误
EN

Stack Overflow用户
提问于 2013-06-25 14:13:48
回答 3查看 3.1K关注 0票数 24

在Python 3中运行此代码后:

代码语言:javascript
复制
import pdb

def foo():
    nums = [1, 2, 3]
    a = 5
    pdb.set_trace()

foo()

以下表达式有效:

代码语言:javascript
复制
(Pdb) print(nums)
[1, 2, 3]

(Pdb) print(a)
5

(Pdb) [x for x in nums]
[1, 2, 3]

但是下面的表达式失败了:

代码语言:javascript
复制
(Pdb) [x*a for x in nums]
*** NameError: global name 'a' is not defined

上面的代码在Python2.7中运行良好。

这是一个bug,还是我遗漏了什么?

更新:查看新的接受答案。这确实是一个错误(或有问题的设计),现在已经通过在pdb中引入新的命令和模式来解决。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-06-04 06:48:46

如果您在ipdb会话中键入interact,您将获得一个交互式会话,并且列表理解在此模式下确实会按预期工作

来源:http://bugs.python.org/msg215963

票数 24
EN

Stack Overflow用户

发布于 2013-06-25 14:34:43

它工作得非常好:

代码语言:javascript
复制
>>> import pdb
>>> def f(seq):
...     pdb.set_trace()
... 
>>> f([1,2,3])
--Return--
> <stdin>(2)f()->None
(Pdb) [x for x in seq]
[1, 2, 3]
(Pdb) [x in seq for x in seq]
[True, True, True]

如果不显示您实际在做什么,没有人能告诉您为什么在您的特定情况下会得到一个NameError

python3列表理解中的TL;DR实际上是具有自己堆栈框架的函数,并且您不能从内部堆栈框架访问seq变量,该变量是test的参数。相反,它被视为全局(因此,找不到它)。

您所看到的是python2和python3中列表理解的不同实现。在Python2中,列表理解实际上是for循环的缩写,您可以在字节码中清楚地看到这一点:

代码语言:javascript
复制
>>> def test(): [x in seq for x in seq]
... 
>>> dis.dis(test)
  1           0 BUILD_LIST               0
              3 LOAD_GLOBAL              0 (seq)
              6 GET_ITER            
        >>    7 FOR_ITER                18 (to 28)
             10 STORE_FAST               0 (x)
             13 LOAD_FAST                0 (x)
             16 LOAD_GLOBAL              0 (seq)
             19 COMPARE_OP               6 (in)
             22 LIST_APPEND              2
             25 JUMP_ABSOLUTE            7
        >>   28 POP_TOP             
             29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        

注意字节码是如何包含FOR_ITER循环的。另一方面,在python3中,列表理解实际上是具有自己的堆栈框架的函数:

代码语言:javascript
复制
>>> def test(): [x in seq2 for x in seq]
... 
>>> dis.dis(test)
  1           0 LOAD_CONST               1 (<code object <listcomp> at 0xb6fef160, file "<stdin>", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_GLOBAL              0 (seq) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 POP_TOP              
             14 LOAD_CONST               0 (None) 
             17 RETURN_VALUE      

正如您所看到的,这里没有FOR_ITER,而是一个MAKE_FUNCTIONCALL_FUNCTION字节码。如果我们检查列表理解的代码,我们可以理解绑定是如何设置的:

代码语言:javascript
复制
>>> test.__code__.co_consts[1]
<code object <listcomp> at 0xb6fef160, file "<stdin>", line 1>
>>> test.__code__.co_consts[1].co_argcount   # it has one argument
1
>>> test.__code__.co_consts[1].co_names      # global variables
('seq2',)
>>> test.__code__.co_consts[1].co_varnames   # local variables
('.0', 'x')

这里,.0是函数的唯一参数。x是循环的局部变量,seq2全局变量。请注意,列表理解参数.0是从seq获得的迭代量,而不是seq本身。(请参阅上面dis输出中的GET_ITER操作码)。通过一个更复杂的示例,这一点更加清楚:

代码语言:javascript
复制
>>> def test():
...     [x in seq for x in zip(seq, a)]
... 
>>> dis.dis(test)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0xb7196f70, file "<stdin>", line 2>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_GLOBAL              0 (zip) 
              9 LOAD_GLOBAL              1 (seq) 
             12 LOAD_GLOBAL              2 (a) 
             15 CALL_FUNCTION            2 
             18 GET_ITER             
             19 CALL_FUNCTION            1 
             22 POP_TOP              
             23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE 
>>> test.__code__.co_consts[1].co_varnames
('.0', 'x')

在这里,您可以看到list- can的惟一参数是从zip(seq, a)获得的.0,它总是用iterable表示。seqa本身不会传递给list- themselves。只有iter(zip(seq, a))被传递到list-comprehension中。

我们必须做的另一个观察是,当您运行pdb时,您不能从您想要定义的函数访问当前函数的上下文。例如,以下代码在python2和python3上都失败:

代码语言:javascript
复制
>>> import pdb
>>> def test(seq): pdb.set_trace()
... 
>>> test([1,2,3])
--Return--
> <stdin>(1)test()->None
(Pdb) def test2(): print(seq)
(Pdb) test2()
*** NameError: global name 'seq' is not defined

它之所以失败,是因为在定义test2时,seq变量被视为全局变量,但它实际上是test函数中的局部变量,因此无法访问。

您看到的行为类似于以下场景:

代码语言:javascript
复制
#python 2 no error
>>> class A(object):
...     x = 1
...     L = [x for _ in range(3)]
... 
>>> 

#python3 error!
>>> class A(object):
...     x = 1
...     L = [x for _ in range(3)]
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in A
  File "<stdin>", line 3, in <listcomp>
NameError: global name 'x' is not defined

第一个不会给出错误,因为它基本上等同于:

代码语言:javascript
复制
>>> class A(object):
...     x = 1
...     L = []
...     for _ in range(3): L.append(x)
... 

因为列表理解在字节码中是“扩展的”。在python3中,它失败了,因为您实际上正在定义一个函数,并且不能从嵌套函数作用域访问类作用域:

代码语言:javascript
复制
>>> class A(object):
...     x = 1
...     def test():
...             print(x)
...     test()
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in A
  File "<stdin>", line 4, in test
NameError: global name 'x' is not defined

请注意,genexp在python2上作为函数实现,实际上您可以看到它们的类似行为(在python2和python3上):

代码语言:javascript
复制
>>> import pdb
>>> def test(seq): pdb.set_trace()
... 
>>> test([1,2,3])
--Return--
> <stdin>(1)test()->None
(Pdb) list(x in seq for x in seq)
*** Error in argument: '(x in seq for x in seq)'

在这里,pdb没有给出更多细节,但失败的原因与此完全相同。

总而言之:这不是pdb中的错误,而是python实现作用域的方式。更改这一点以允许您尝试在pdb中做的事情将需要在如何处理函数方面进行一些重大更改,我不知道是否可以在不修改解释器的情况下做到这一点。

请注意,当使用嵌套列表理解时,嵌套循环在字节码中扩展,就像python2中的列表理解一样:

代码语言:javascript
复制
>>> import dis
>>> def test(): [x + y for x in seq1 for y in seq2]
... 
>>> dis.dis(test)
  1           0 LOAD_CONST               1 (<code object <listcomp> at 0xb71bf5c0, file "<stdin>", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_GLOBAL              0 (seq1) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 POP_TOP              
             14 LOAD_CONST               0 (None) 
             17 RETURN_VALUE         
>>> # The only argument to the listcomp is seq1
>>> import types
>>> func = types.FunctionType(test.__code__.co_consts[1], globals())
>>> dis.dis(func)
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                29 (to 38) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (seq2) 
             15 GET_ITER             
        >>   16 FOR_ITER                16 (to 35) 
             19 STORE_FAST               2 (y) 
             22 LOAD_FAST                1 (x) 
             25 LOAD_FAST                2 (y) 
             28 BINARY_ADD           
             29 LIST_APPEND              3 
             32 JUMP_ABSOLUTE           16 
        >>   35 JUMP_ABSOLUTE            6 
        >>   38 RETURN_VALUE        

如您所见,listcomp的字节码在seq2上有一个显式的FOR_ITER。这个显式的FOR_ITER位于listcomp函数中,因此对作用域的限制仍然适用(例如,seq2作为全局变量加载)。

事实上,我们可以使用pdb来确认这一点

代码语言:javascript
复制
>>> import pdb
>>> def test(seq1, seq2): pdb.set_trace()
... 
>>> test([1,2,3], [4,5,6])
--Return--
> <stdin>(1)test()->None
(Pdb) [x + y for x in seq1 for y in seq2]
*** NameError: global name 'seq2' is not defined
(Pdb) [x + y for x in non_existent for y in seq2]
*** NameError: name 'non_existent' is not defined

注意NameError是关于seq2而不是seq1(作为函数参数传递),并注意将第一个可迭代名称更改为不存在的东西是如何改变NameError的(这意味着在第一种情况下seq1被成功传递)。

票数 10
EN

Stack Overflow用户

发布于 2013-06-25 14:26:17

我只是不明白为什么你需要这样做,如果你想为seq中的每个元素生成True列表,那么为什么不为seq中的x生成True-我猜你需要在尝试这种事情之前首先分配一个本地副本。

票数 -5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17290314

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档