关于python:在多处理池中使用时,不会正确引发自定义异常

Custom exceptions are not raised properly when used in Multiprocessing Pool

我正在观察Python 3.3.4中我希望帮助理解的行为:为什么在正常执行函数时正确引发异常,而不是在函数池中执行函数时?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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'

相反,如果我只是调用该函数,它似乎正确处理异常:

1
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在进程池中运行时表现不同?


问题是您的异常类在其__init__方法中具有非可选参数,但是当您调用超类__init__方法时,您不会传递这些参数。当您的异常实例被multiprocessing代码取消时,这会导致新的异常。

这是Python异常的一个长期存在的问题,你可以在这个错误报告中阅读相当多的问题历史(其中一部分基本问题与酸洗异常是固定的,但不是你的部分'重击)。

总结一下这个问题:Python的base Exception类将它的__init__方法接收的所有参数放入名为args的属性中。这些参数被放入pickle数据中,当流被取消时,它们被传递给新创建的对象的__init__方法。如果Exception.__init__收到的参数数量与子类所期望的数量不同,则在unpickling时会出现错误。

该问题的解决方法是将自定义异常类在其__init__方法中需要的所有参数传递给超类__init__

1
2
3
4
class ModuleException_2(AllModuleExceptions):
    def __init__(self, message2):
        super(ModuleException_2, self).__init__(message2) # the change is here!
        self.e_string ="Message: {}".format(message2)

另一个可能的解决方法是根本不调用超类__init__方法(这是上面链接的bug中允许的修复),但由于这通常是子类的不良行为,我不能真正推荐它。


你的ModuleException_2.__init__在破坏时失败了。

我能够通过将签名更改为来解决问题

1
2
3
4
5
class ModuleException_2(AllModuleExceptions):
    def __init__(self, message2=None):
        super(ModuleException_2, self).__init__()
        self.e_string ="Message: {}".format(message2)
        return

但最好看看Pickling Class Instances以确保干净的实施。