我在一个模块中定义了一个namedtuple类型,该模块由两个类foo和bar组成,它们在模块的唯一文件mod.py
中定义。我能够创建foo和bar的实例而不会出现问题并将它们腌制。我现在正在尝试对其进行Cythonize,以便可以将模块作为字节码分发。
模块文件结构如下:
./mod.pyx
./setup.py
./demo.py
“ mod.pyx”的内容为:
import collections
foo = collections.namedtuple('foo', 'A B')
class bar:
def __init__(self,A,B):
self.A = A
self.B = B
setup.py
的内容是:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
setup(
ext_modules= cythonize([Extension('mod', ['mod.pyx'])])
)
我使用命令python setup.py build_ext --inplace
将其cythonize,它会创建编译后的模块文件:
./mod.cp37-win_amd64.pyd
运行以下demo.py
:
import mod, pickle
ham = mod.foo(1,2)
spam = mod.bar(1,2)
print(pickle.dumps(spam))
print(pickle.dumps(ham))
成功腌制spam
(类bar
的实例,但在ham
(命名元组foo
的实例)上失败,并显示错误消息:
PicklingError: Can't pickle <class 'importlib._bootstrap.foo'>: attribute lookup foo on importlib._bootstrap failed
如果重要的话,这一切都在Python 3.7中完成。即使Python能够毫无问题地创建实例,Pickle似乎也无法找到mod.foo
的类定义。我知道namedtuple在返回类的命名方面有一些怪异的行为,我承认我是包装Cython模块的相对新手。
一点点谷歌搜索发现了namedtuples和Cython的一些已知问题,所以我想知道这是否可能是已知问题的一部分,或者我只是包装了错误的模块。
答案 0 :(得分:3)
为了使pickle
工作,必须设置__module__
类型的属性foo
,并且属性应为mod
。
namedtuple
使用a trick/heuristic(即在sys._getframe(1).f_globals
中查找)来获取以下信息:
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
...
# For pickling to work, the __module__ variable needs to be set to the frame
# where the named tuple is created. Bypass this step in environments where
# sys._getframe is not defined (Jython for example) or sys._getframe is not
# defined for arguments greater than 0 (IronPython), or where the user has
# specified a particular module.
if module is None:
try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
if module is not None:
result.__module__ = module
...
Cython或C扩展的问题在于,这种启发式将不起作用,并且_sys._getframe(1).f_globals.get('__name__', '__main__')
将产生importlib._bootstrap
而不是mod
。
要解决此问题,您需要将module
-名称正确传递给namedtuple
-factory(如代码注释中所述),即:
foo = collections.namedtuple('foo', 'A B', module='mod')
或使其更通用:
foo = collections.namedtuple('foo', 'A B', module=__name__)
现在,在导入后,foo.__module__
就是mod
所期望的pickle
,并且可以腌制ham
。
顺便说一下,bar
函数的腌制是因为Cython在构造类时显式设置了正确的__module__
属性(即mod
)。