通常,对namedtuple实例的酸洗成功,但是在对模块进行Cythonized时酸洗失败

时间:2019-03-18 15:06:24

标签: python python-3.x cython

我在一个模块中定义了一个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的一些已知问题,所以我想知道这是否可能是已知问题的一部分,或者我只是包装了错误的模块。

1 个答案:

答案 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)。