覆盖的 __setitem__ 调用串行工作,但在 apply_async 调用中中断
Overridden __setitem__ call works in serial but breaks in apply_async call
我已经和这个问题斗争了一段时间了,我终于设法缩小了问题的范围并创建了一个最小的工作示例。
问题总结就是我有一个class继承自一个dict
,方便解析misc。输入文件。我已经覆盖了 __setitem__
调用以支持我们输入文件中部分的递归索引(例如 parser['some.section.variable']
等同于 parser['some']['section']['variable']
)。这对我们来说已经工作了一年多了,但是我们只是 运行 在通过 multiprocessing.apply_async
调用传递这些 Parser
class 时遇到了问题。
下面显示的是最小工作示例 - 显然 __setitem__
调用没有做任何特别的事情,但它访问一些 class 属性很重要,比如 self.section_delimiter
- 这是它在哪里打破。它不会在初始调用或串行函数调用中中断。但是,当您使用 apply_async
调用 some_function
(它也不执行任何操作)时,它会崩溃。
import multiprocessing as mp
import numpy as np
class Parser(dict):
def __init__(self, file_name : str = None):
print('\t__init__')
super().__init__()
self.section_delimiter = "."
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
dict.__setitem__(self, key, value)
def some_function(parser):
pass
if __name__ == "__main__":
print("Initialize creation/setting")
parser = Parser()
parser['x'] = 1
print("Single serial call works fine")
some_function(parser)
print("Parallel async call breaks on line 16?")
pool = mp.Pool(1)
for i in range(1):
pool.apply_async(some_function, (parser,))
pool.close()
pool.join()
如果你运行下面的代码,你会得到以下输出
Initialize creation/setting
__init__
__setitem__
Single serial call works fine
Parallel async call breaks on line 16?
__setitem__
Process ForkPoolWorker-1:
Traceback (most recent call last):
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
self.run()
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/pool.py", line 110, in worker
task = get()
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/queues.py", line 354, in get
return _ForkingPickler.loads(res)
File "test_apply_async.py", line 13, in __setitem__
self.section_delimiter
AttributeError: 'Parser' object has no attribute 'section_delimiter'
非常感谢任何帮助。我花了相当多的时间来追踪这个错误并重现了一个最小的例子。我不仅愿意修复它,而且还想清楚地填补我对这些 apply_async
和 inheritance/overridden 方法如何相互作用的理解上的一些空白。
如果您需要更多信息,请告诉我。
非常感谢!
以撒
原因
问题的原因是 multiprocessing
序列化和反序列化您的 Parser
对象以跨进程边界移动其数据。这是在反序列化 classes 时使用 pickle. By default pickle does not call __init__()
完成的。因此,当反序列化程序调用 __setitem__()
来恢复字典中的项目时,未设置 self.section_delimiter
并且出现错误:
AttributeError: 'Parser' object has no attribute 'section_delimiter'
仅使用 pickle 而不使用多处理会产生相同的错误:
import pickle
parser = Parser()
parser['x'] = 1
data = pickle.dumps(parser)
copy = pickle.loads(data) # Same AttributeError here
反序列化适用于没有项目的对象,section_delimiter
的值将被恢复:
import pickle
parser = Parser()
parser.section_delimiter = "|"
data = pickle.dumps(parser)
copy = pickle.loads(data)
print(copy.section_delimiter) # Prints "|"
所以从某种意义上说,你只是不幸的是 pickle 在恢复你的 Parser
.
的其余状态之前调用了 __setitem__()
解决方法
您可以通过在 __new__()
中设置 section_delimiter
并通过实现 __getnewargs__()
:
告诉 pickle 将哪些参数传递给 __new__()
来解决此问题
def __new__(cls, *args):
self = super(Parser, cls).__new__(cls)
self.section_delimiter = args[0] if args else "."
return self
def __getnewargs__(self):
return (self.section_delimiter,)
__getnewargs__()
returns 参数元组。因为section_delimiter
是在__new__()
里设置的,所以不用再在__init__()
里设置了。
这是你的Parser
class修改后的代码:
class Parser(dict):
def __init__(self, file_name : str = None):
print('\t__init__')
super().__init__()
def __new__(cls, *args):
self = super(Parser, cls).__new__(cls)
self.section_delimiter = args[0] if args else "."
return self
def __getnewargs__(self):
return (self.section_delimiter,)
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
dict.__setitem__(self, key, value)
更简单的解决方案
pickle 在您的 Parser
对象上调用 __setitem__()
的原因是因为它 是 字典。如果您的 Parser
只是一个 class 恰好实现了 __setitem__()
和 __getitem__()
并且 有 字典来实现这些调用然后 pickle不会调用 __setitem__()
并且序列化将在没有额外代码的情况下工作:
class Parser:
def __init__(self, file_name : str = None):
print('\t__init__')
self.dict = { }
self.section_delimiter = "."
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
self.dict[key] = value
def __getitem__(self, key):
return self.dict[key]
所以如果没有其他原因让你的 Parser
成为字典,我就不会在这里使用继承。
我已经和这个问题斗争了一段时间了,我终于设法缩小了问题的范围并创建了一个最小的工作示例。
问题总结就是我有一个class继承自一个dict
,方便解析misc。输入文件。我已经覆盖了 __setitem__
调用以支持我们输入文件中部分的递归索引(例如 parser['some.section.variable']
等同于 parser['some']['section']['variable']
)。这对我们来说已经工作了一年多了,但是我们只是 运行 在通过 multiprocessing.apply_async
调用传递这些 Parser
class 时遇到了问题。
下面显示的是最小工作示例 - 显然 __setitem__
调用没有做任何特别的事情,但它访问一些 class 属性很重要,比如 self.section_delimiter
- 这是它在哪里打破。它不会在初始调用或串行函数调用中中断。但是,当您使用 apply_async
调用 some_function
(它也不执行任何操作)时,它会崩溃。
import multiprocessing as mp
import numpy as np
class Parser(dict):
def __init__(self, file_name : str = None):
print('\t__init__')
super().__init__()
self.section_delimiter = "."
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
dict.__setitem__(self, key, value)
def some_function(parser):
pass
if __name__ == "__main__":
print("Initialize creation/setting")
parser = Parser()
parser['x'] = 1
print("Single serial call works fine")
some_function(parser)
print("Parallel async call breaks on line 16?")
pool = mp.Pool(1)
for i in range(1):
pool.apply_async(some_function, (parser,))
pool.close()
pool.join()
如果你运行下面的代码,你会得到以下输出
Initialize creation/setting
__init__
__setitem__
Single serial call works fine
Parallel async call breaks on line 16?
__setitem__
Process ForkPoolWorker-1:
Traceback (most recent call last):
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
self.run()
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/pool.py", line 110, in worker
task = get()
File "/home/ijw/miniconda3/lib/python3.7/multiprocessing/queues.py", line 354, in get
return _ForkingPickler.loads(res)
File "test_apply_async.py", line 13, in __setitem__
self.section_delimiter
AttributeError: 'Parser' object has no attribute 'section_delimiter'
非常感谢任何帮助。我花了相当多的时间来追踪这个错误并重现了一个最小的例子。我不仅愿意修复它,而且还想清楚地填补我对这些 apply_async
和 inheritance/overridden 方法如何相互作用的理解上的一些空白。
如果您需要更多信息,请告诉我。
非常感谢!
以撒
原因
问题的原因是 multiprocessing
序列化和反序列化您的 Parser
对象以跨进程边界移动其数据。这是在反序列化 classes 时使用 pickle. By default pickle does not call __init__()
完成的。因此,当反序列化程序调用 __setitem__()
来恢复字典中的项目时,未设置 self.section_delimiter
并且出现错误:
AttributeError: 'Parser' object has no attribute 'section_delimiter'
仅使用 pickle 而不使用多处理会产生相同的错误:
import pickle
parser = Parser()
parser['x'] = 1
data = pickle.dumps(parser)
copy = pickle.loads(data) # Same AttributeError here
反序列化适用于没有项目的对象,section_delimiter
的值将被恢复:
import pickle
parser = Parser()
parser.section_delimiter = "|"
data = pickle.dumps(parser)
copy = pickle.loads(data)
print(copy.section_delimiter) # Prints "|"
所以从某种意义上说,你只是不幸的是 pickle 在恢复你的 Parser
.
__setitem__()
解决方法
您可以通过在 __new__()
中设置 section_delimiter
并通过实现 __getnewargs__()
:
__new__()
来解决此问题
def __new__(cls, *args):
self = super(Parser, cls).__new__(cls)
self.section_delimiter = args[0] if args else "."
return self
def __getnewargs__(self):
return (self.section_delimiter,)
__getnewargs__()
returns 参数元组。因为section_delimiter
是在__new__()
里设置的,所以不用再在__init__()
里设置了。
这是你的Parser
class修改后的代码:
class Parser(dict):
def __init__(self, file_name : str = None):
print('\t__init__')
super().__init__()
def __new__(cls, *args):
self = super(Parser, cls).__new__(cls)
self.section_delimiter = args[0] if args else "."
return self
def __getnewargs__(self):
return (self.section_delimiter,)
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
dict.__setitem__(self, key, value)
更简单的解决方案
pickle 在您的 Parser
对象上调用 __setitem__()
的原因是因为它 是 字典。如果您的 Parser
只是一个 class 恰好实现了 __setitem__()
和 __getitem__()
并且 有 字典来实现这些调用然后 pickle不会调用 __setitem__()
并且序列化将在没有额外代码的情况下工作:
class Parser:
def __init__(self, file_name : str = None):
print('\t__init__')
self.dict = { }
self.section_delimiter = "."
def __setitem__(self, key, value):
print('\t__setitem__')
self.section_delimiter
self.dict[key] = value
def __getitem__(self, key):
return self.dict[key]
所以如果没有其他原因让你的 Parser
成为字典,我就不会在这里使用继承。