提问者:小点点

为什么在numpy. genfromtxt()中使用间接定义的转换器会失败并出现错误“RecursionError:超过最大递归深度”?


我猜这是一个只有那些喜欢挖掘Python回溯源代码挑战的人才能回答的问题…但是也许有人知道答案。

这应该很容易重现,请参阅下面的代码(我假设根据您的硬件和sys. setpostsionlimited()的值,您可能需要从我的值2000增加最大迭代次数)。

它是numpy. genfromtxt读取一个由单个字符0组成的1列1行的CSV文件。当“转换器”被显式设置(下面注释掉)时,一切都很好,而且很快。当“转换器”被间接设置为如代码所示,Python正在做一些完全不必要的递归,代码在1400到1500次迭代之间的某个地方失败(在我的计算机上),错误为“RecursionError:超过最大递归深度”。在代码失败之前,随着迭代(以及可能的递归深度)的增加,它变得越来越慢。Traceback指向所涉及的源代码,但我不知道如何深入研究它。

问题是:为什么这段代码不能和显式设置“转换器”的代码完全一样工作?这是bug,还是有意义;也就是说,我的代码是坏的?

#Spyder 3.3.3 | Python 3.7.3 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10 

import numpy as np

the_converters = {'data': lambda s : 0} 

jcount = 0
while jcount < 2000:

    jcount = jcount + 1
    print(jcount)

    the_array = np.genfromtxt('recursion_debug.csv', delimiter =',', \
                             names = 'data', \
                             converters = the_converters, \
                             #converters = {'data': lambda s : 0}, \
                             )

共1个答案

匿名用户

In [1]: txt="""0,0 
   ...: 0,0"""         
In [14]: cvt = {'data':lambda s: 10}                                                                                     
In [15]: cvt                                                                                                             
Out[15]: {'data': <function __main__.<lambda>(s)>}
In [16]: np.genfromtxt(txt.splitlines(),delimiter=',',usecols=[0],names='data',converters=cvt)                           
Out[16]: array([10, 10])
In [17]: cvt                                                                                                             
Out[17]: 
{'data': <function __main__.<lambda>(s)>,
 0: functools.partial(<function genfromtxt.<locals>.tobytes_first at 0x7f5e71154bf8>, conv=<function <lambda> at 0x7f5e70928b70>)}

genfromtxt正在修改cvt对象(就地),并且这种效果是累积的:

In [18]: np.genfromtxt(txt.splitlines(),delimiter=',',usecols=[0],names='data',converters=cvt)                           
Out[18]: array([10, 10])
In [19]: cvt                                                                                                             
Out[19]: 
{'data': <function __main__.<lambda>(s)>,
 0: functools.partial(<function genfromtxt.<locals>.tobytes_first at 0x7f5e82ea4bf8>, conv=functools.partial(<function genfromtxt.<locals>.tobytes_first at 0x7f5e71154bf8>, conv=<function <lambda> at 0x7f5e70928b70>))}

请注意,命名键值不会更改;相反,它使用修改后的转换器添加了一个列号键。

相反,如果我们在线创建字典,并仅提供lambda(或函数),则不会修改该函数:

In [26]: cvt = lambda s: 10                                                                                              
In [27]: np.genfromtxt(txt.splitlines(),delimiter=',',usecols=[0],names='data',converters={'data':cvt})                  
Out[27]: array([10, 10])
In [28]: cvt                                                                                                             
Out[28]: <function __main__.<lambda>(s)>

现在创建一个也显示输入字符串的函数:

In [53]: def foo(s): 
    ...:     print(s) 
    ...:     return '10' 
    ...:                                                                                                                 
In [54]: cvt = {'data':foo}                                                                                              

如果我指定编码,字典仍然被修改(新键),但函数没有被修改:

In [55]: np.genfromtxt(txt.splitlines(),delimiter=',',usecols=[0],names='data',converters=cvt, encoding=None)            
0
0
0
Out[55]: array(['10', '10'], dtype='<U2')
In [56]: cvt                                                                                                             
Out[56]: {'data': <function __main__.foo(s)>, 0: <function __main__.foo(s)>}

如果没有编码(或默认的“字节”),则添加tobytes包装器,并将字节字符串传递给我的函数:

In [57]: np.genfromtxt(txt.splitlines(),delimiter=',',usecols=[0],names='data',converters=cvt)                           
b'0'
b'0'
b'0'
b'0'
Out[57]: array(['10', '10'], dtype='<U2')
In [58]: cvt                                                                                                             
Out[58]: 
{'data': <function __main__.foo(s)>,
 0: functools.partial(<function genfromtxt.<locals>.tobytes_first at 0x7f5e82e9c730>, conv=<function foo at 0x7f5e7113e268>)}

===

添加的代码是旧的Py2到Py3字节到unicode开关的一部分:

   elif byte_converters:
        # converters may use decode to workaround numpy's old behaviour,
        # so encode the string again before passing to the user converter
        def tobytes_first(x, conv):
            if type(x) is bytes:
                return conv(x)
            return conv(x.encode("latin1"))
        import functools
        user_conv = functools.partial(tobytes_first, conv=conv)
    else:
        user_conv = conv