2

我有一个路易吉管道。我们有很多定期更改的外部文件,我们希望能够从元数据构建管道。

我动态创建类,并找到了两种方法:

使用执行:

exec("""
class {system}(DeliverySystem):
    pass
""".format(system='ClassUsingExec'))

使用类型:

name = 'ClassUsingType'
globals()[name] = type(name, (DeliverySystem,),{})

这两种方法在单线程环境中都可以正常工作,但是当我开始运行 luigi 并且有许多工作人员产生子进程时,exec 版本很好,但是类型版本给出了本文和这篇文章中描述的错误参阅它们以获得更完整的堆栈痕迹):

PicklingError: Can't pickle <class 'abc.ClassUsingType'>: attribute lookup abc.ClassUsingType failed.

我可以在两者之间找到的唯一区别是模块:

print(ClassUsingExec.__dict__) #=>

mappingproxy({'__module__': '__main__',
              '__doc__': None,
              '__abstractmethods__': frozenset(),
              '_abc_impl': <_abc_data at 0x15b5063c120>,
              '_namespace_at_class_time': ''})


print(ClassUsingType.__dict__) #=>

mappingproxy({'__module__': 'abc',
              '__doc__': None,
              '__abstractmethods__': frozenset(),
              '_abc_impl': <_abc_data at 0x15b3f870450>,
              '_namespace_at_class_time': ''})

似乎模块不同,这可能是差异的来源。

使用 Python 3.6、Windows 10、luigi 2.8.9。

问题:

有没有办法用来type创建一个类,以便它的模块是定义它的模块,而不是在abc

这些方法之间是否还有其他一些区别?根据这篇文章,应该没有区别,但我发现情况并非如此。

4

1 回答 1

3

出现问题的原因是:

  • 在 Windows 中,子进程无权访问父变量。
  • Luigi Tasks 使用 Register(和 ABC 的扩展)作为它的元类。
  • 当动态创建一个具有ABC(抽象基类)作为元类的类时,该类的模块将是abc,而不是定义该类的模块的名称。

当一个工人被分配一个任务时,它会去模块加载任务。由于模块设置为abc而不是动态创建类的模块,因此它将失败。

为了让它工作,只需要修改类创建来修改模块:

type(name, (DeliverySystem,),{})

变成

type(name, (DeliverySystem,),{'__module__':__name__})

现在,当工人被分配任务时,它将进入正确的模块并重新创建类,并且一切正常!

于 2019-11-05T17:09:11.497 回答