我猜这是一个只有那些喜欢挖掘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}, \
)
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