在 Python 中动态创建 class 时执行 vs 类型
exec vs type when dynamically creating class in Python
我有一个 luigi 管道。我们有很多定期更改的外部文件,我们希望能够从元数据构建管道。
我动态创建 classes,并找到了两种方法:
使用执行程序:
exec("""
class {system}(DeliverySystem):
pass
""".format(system='ClassUsingExec'))
使用类型:
name = 'ClassUsingType'
globals()[name] = type(name, (DeliverySystem,),{})
这两个在单线程环境中都可以正常工作,但是当我启动 运行 luigi 时有很多工作人员产生子进程,exec 版本很好但是类型版本给出了 [=18 中描述的错误=](查看它们以获得更完整的堆栈跟踪):
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
创建一个class,使其模块是定义它的模块,而不是abc
?
我还遗漏了这些方法之间的其他区别吗?根据 this post 应该没有区别,但我发现情况并非如此。
出现问题是因为:
- 在 Windows 中,子进程无法访问父变量。
- Luigi Tasks 使用 Register(和 ABC 的扩展)作为其元class。
- 当动态创建一个 class 作为元 class 的
ABC
(抽象基础 Class)时,class 的模块将是 abc
,而不是定义 class 的模块的名称。
当一个worker被分配到一个Task时,它会去模块加载任务。由于模块设置为 abc
而不是动态创建 class 的模块,它将失败。
要让它工作,只需要修改class创建修改模块:
type(name, (DeliverySystem,),{})
变成
type(name, (DeliverySystem,),{'__module__':__name__})
现在,当 worker 被分配任务时,它将进入正确的模块并重新创建 class,一切都会正常进行!
我有一个 luigi 管道。我们有很多定期更改的外部文件,我们希望能够从元数据构建管道。
我动态创建 classes,并找到了两种方法:
使用执行程序:
exec("""
class {system}(DeliverySystem):
pass
""".format(system='ClassUsingExec'))
使用类型:
name = 'ClassUsingType'
globals()[name] = type(name, (DeliverySystem,),{})
这两个在单线程环境中都可以正常工作,但是当我启动 运行 luigi 时有很多工作人员产生子进程,exec 版本很好但是类型版本给出了 [=18 中描述的错误=](查看它们以获得更完整的堆栈跟踪):
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
创建一个class,使其模块是定义它的模块,而不是abc
?
我还遗漏了这些方法之间的其他区别吗?根据 this post 应该没有区别,但我发现情况并非如此。
出现问题是因为:
- 在 Windows 中,子进程无法访问父变量。
- Luigi Tasks 使用 Register(和 ABC 的扩展)作为其元class。
- 当动态创建一个 class 作为元 class 的
ABC
(抽象基础 Class)时,class 的模块将是abc
,而不是定义 class 的模块的名称。
当一个worker被分配到一个Task时,它会去模块加载任务。由于模块设置为 abc
而不是动态创建 class 的模块,它将失败。
要让它工作,只需要修改class创建修改模块:
type(name, (DeliverySystem,),{})
变成
type(name, (DeliverySystem,),{'__module__':__name__})
现在,当 worker 被分配任务时,它将进入正确的模块并重新创建 class,一切都会正常进行!