在多处理池中使用时未正确引发自定义异常
Custom exceptions are not raised properly when used in Multiprocessing Pool
问题
我正在观察 Python 3.3.4 中的行为,我想帮助理解:为什么当函数正常执行时我的异常会正确引发,但当函数在工作池中执行时却不会?
代码
import multiprocessing
class AllModuleExceptions(Exception):
"""Base class for library exceptions"""
pass
class ModuleException_1(AllModuleExceptions):
def __init__(self, message1):
super(ModuleException_1, self).__init__()
self.e_string = "Message: {}".format(message1)
return
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
def func_that_raises_exception(arg1, arg2):
result = arg1 + arg2
raise ModuleException_1("Something bad happened")
def func(arg1, arg2):
try:
result = func_that_raises_exception(arg1, arg2)
except ModuleException_1:
raise ModuleException_2("We need to halt main") from None
return result
pool = multiprocessing.Pool(2)
results = pool.starmap(func, [(1,2), (3,4)])
pool.close()
pool.join()
print(results)
此代码产生此错误:
Exception in thread Thread-3:
Traceback (most recent call last):
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 921, in _bootstrap_inner
self.run()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 869, in run
self._target(*self._args, **self._kwargs)
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/pool.py", line 420, in _handle_results
task = get()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/connection.py", line 251, in recv
return ForkingPickler.loads(buf.getbuffer())
TypeError: __init__() missing 1 required positional argument: 'message2'
相反,如果我简单地调用函数,它似乎可以正确处理异常:
print(func(1, 2))
产生:
Traceback (most recent call last):
File "exceptions.py", line 40, in
print(func(1, 2))
File "exceptions.py", line 30, in func
raise ModuleException_2("We need to halt main") from None
__main__.ModuleException_2
为什么 ModuleException_2
在进程池中 运行 时表现不同?
您的 ModuleException_2.__init__
在未腌制时失败。
我可以通过将签名更改为
来解决问题
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2=None):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
但最好看看 Pickling Class Instances 以确保干净的实施。
问题是你的异常 classes 在它们的 __init__
方法中有 non-optional 个参数,但是当你调用 superclass __init__
您不传递这些参数的方法。当您的异常实例被 multiprocessing
代码 unpickle 时,这会导致新的异常。
这是一个 long-standing 问题,但有 Python 个例外,您可以在 this bug report 中阅读相当多的问题历史记录(其中一部分底层酸洗异常的问题已修复,但不是您正在点击的部分)。
总结一下这个问题:Python 的基础 Exception
class 将其 __init__
方法接收的所有参数放入名为 args
的属性中。这些参数被放入 pickle
数据中,当流被 unpickled 时,它们被传递给新创建的 object 的 __init__
方法。如果 Exception.__init__
收到的参数数量与 child class 预期的不一样,你会在 unpickling 时出错。
该问题的解决方法是将您自定义异常 classes 在其 __init__
方法中需要的所有参数传递给 superclass __init__
:
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__(message2) # the change is here!
self.e_string = "Message: {}".format(message2)
另一种可能的修复方法是根本不调用 superclass __init__
方法(这是上面链接的错误中允许的修复方法),但由于这通常是一个糟糕的行为subclass,我真的不推荐它。
问题
我正在观察 Python 3.3.4 中的行为,我想帮助理解:为什么当函数正常执行时我的异常会正确引发,但当函数在工作池中执行时却不会?
代码
import multiprocessing
class AllModuleExceptions(Exception):
"""Base class for library exceptions"""
pass
class ModuleException_1(AllModuleExceptions):
def __init__(self, message1):
super(ModuleException_1, self).__init__()
self.e_string = "Message: {}".format(message1)
return
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
def func_that_raises_exception(arg1, arg2):
result = arg1 + arg2
raise ModuleException_1("Something bad happened")
def func(arg1, arg2):
try:
result = func_that_raises_exception(arg1, arg2)
except ModuleException_1:
raise ModuleException_2("We need to halt main") from None
return result
pool = multiprocessing.Pool(2)
results = pool.starmap(func, [(1,2), (3,4)])
pool.close()
pool.join()
print(results)
此代码产生此错误:
Exception in thread Thread-3:
Traceback (most recent call last):
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 921, in _bootstrap_inner
self.run()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/threading.py", line 869, in run
self._target(*self._args, **self._kwargs)
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/pool.py", line 420, in _handle_results
task = get()
File "/user/peteoss/encap/Python-3.4.2/lib/python3.4/multiprocessing/connection.py", line 251, in recv
return ForkingPickler.loads(buf.getbuffer()) TypeError: __init__() missing 1 required positional argument: 'message2'
相反,如果我简单地调用函数,它似乎可以正确处理异常:
print(func(1, 2))
产生:
Traceback (most recent call last):
File "exceptions.py", line 40, in
print(func(1, 2))
File "exceptions.py", line 30, in func
raise ModuleException_2("We need to halt main") from None
__main__.ModuleException_2
为什么 ModuleException_2
在进程池中 运行 时表现不同?
您的 ModuleException_2.__init__
在未腌制时失败。
我可以通过将签名更改为
来解决问题class ModuleException_2(AllModuleExceptions):
def __init__(self, message2=None):
super(ModuleException_2, self).__init__()
self.e_string = "Message: {}".format(message2)
return
但最好看看 Pickling Class Instances 以确保干净的实施。
问题是你的异常 classes 在它们的 __init__
方法中有 non-optional 个参数,但是当你调用 superclass __init__
您不传递这些参数的方法。当您的异常实例被 multiprocessing
代码 unpickle 时,这会导致新的异常。
这是一个 long-standing 问题,但有 Python 个例外,您可以在 this bug report 中阅读相当多的问题历史记录(其中一部分底层酸洗异常的问题已修复,但不是您正在点击的部分)。
总结一下这个问题:Python 的基础 Exception
class 将其 __init__
方法接收的所有参数放入名为 args
的属性中。这些参数被放入 pickle
数据中,当流被 unpickled 时,它们被传递给新创建的 object 的 __init__
方法。如果 Exception.__init__
收到的参数数量与 child class 预期的不一样,你会在 unpickling 时出错。
该问题的解决方法是将您自定义异常 classes 在其 __init__
方法中需要的所有参数传递给 superclass __init__
:
class ModuleException_2(AllModuleExceptions):
def __init__(self, message2):
super(ModuleException_2, self).__init__(message2) # the change is here!
self.e_string = "Message: {}".format(message2)
另一种可能的修复方法是根本不调用 superclass __init__
方法(这是上面链接的错误中允许的修复方法),但由于这通常是一个糟糕的行为subclass,我真的不推荐它。