为什么在ProcessPoolExecutor中引发自定义异常时必须具有默认参数?

时间:2019-06-08 22:28:30

标签: python multithreading exception

我有一个ProcessPoolExecutor,想在其中引发一个自定义异常。但是,只有当它具有默认参数或在超级初始化中使用或绕过该超级初始化功能时,它才有效而不会破坏进程池。 我对此行为一无所知。 在Py3.7和3.8上显示: A process in the process pool was terminated abruptly while the future was running or pending. 有任何想法吗?

from concurrent.futures.process import ProcessPoolExecutor


class PoolBreaker(Exception):
    def __init__(self, num):
        super().__init__()
        self.num = num


class NoPoolBreaker(Exception):
    def __init__(self, num=0):
        super().__init__()
        self.num = num


class NoPoolBreaker2(Exception):
    def __init__(self, num):
        super().__init__(num)
        self.num = num


class NoPoolBreaker3(Exception):
    def __init__(self, num):
        self.num = num


def get_result(job):
    exc = job.exception()
    if type(exc) is PoolBreaker:
        print("PoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker:
        print("NoPoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker2:
        print("NoPoolBreaker2", exc.num)
    elif type(exc) is NoPoolBreaker3:
        print("NoPoolBreaker3", exc.num)
    else:
        print(f"Exception: {str(exc)}")


def work(x: int):
    if x == 0:
        raise PoolBreaker(x)
    elif x == 1:
        raise NoPoolBreaker(x)
    elif x == 2:
        raise NoPoolBreaker2(x)
    elif x == 3:
        raise NoPoolBreaker3(x)


if __name__ == '__main__':
    for num in range(0, 4):
        with ProcessPoolExecutor() as executor:
            job = executor.submit(work, num)
        get_result(job)

2 个答案:

答案 0 :(得分:0)

我也解决了这个问题,对此我感到非常困惑。这只会在ProcessPoolExecutor中发生,而不会在ThreadPoolExecutor中发生。

问题是ProcessPoolExecutor尝试通过第二次创建来创建Exception的副本。为此,ProcessPoolExecutor似乎要检查“上层”类,例如NoPoolBreaker3除外,其中上层类是NoPoolBreaker3本身–属性。由于Exception并不总是拥有整个对象的所有属性,因此它会失败。

如果将打印品添加到构造函数中,您将看到:

from concurrent.futures.process import ProcessPoolExecutor

class PoolBreaker(Exception):
    def __init__(self, num):
        super().__init__()
        print(f"PoolBreaker Constructor with num={num}")
        self.num = num


class NoPoolBreaker1(Exception):
    def __init__(self, num=-9):
        super().__init__()
        print(f"NoPoolBreaker1 Constructor with num={num}")
        self.num = num


class NoPoolBreaker2(Exception):
    def __init__(self, num):
        super().__init__(num)
        print(f"NoPoolBreaker2 Constructor with num={num}")
        self.num = num


class NoPoolBreaker3(Exception):
    def __init__(self, num):
        print(f"NoPoolBreaker3 Constructor with num={num}")
        self.num = num


def get_result(job):
    exc = job.exception()
    if type(exc) is PoolBreaker:
        print("PoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker1:
        print("NoPoolBreaker1", exc.num)
    elif type(exc) is NoPoolBreaker2:
        print("NoPoolBreaker2", exc.num)
    elif type(exc) is NoPoolBreaker3:
        print("NoPoolBreaker3", exc.num)
    else:
        print(f"Exception: {str(exc)}")


def work(x: int):
    if x == 0:
        raise PoolBreaker(x)
    elif x == 1:
        raise NoPoolBreaker1(x)
    elif x == 2:
        raise NoPoolBreaker2(x)
    elif x == 3:
        raise NoPoolBreaker3(x)


if __name__ == '__main__':
    for num in range(0, 4):
        with ProcessPoolExecutor() as executor:
            job = executor.submit(work, num)
        get_result(job)


及其输出(python 3.7.3):

PoolBreaker Constructor with num=0
Exception: A process in the process pool was terminated abruptly while the future was running or pending.
NoPoolBreaker1 Constructor with num=1
NoPoolBreaker1 Constructor with num=-9
NoPoolBreaker1 1
NoPoolBreaker2 Constructor with num=2
NoPoolBreaker2 Constructor with num=2
NoPoolBreaker2 2
NoPoolBreaker3 Constructor with num=3
NoPoolBreaker3 Constructor with num=3
NoPoolBreaker3 3

如您所见,异常的构造函数被调用了两次,但让我们仔细研究一下这些情况:

  • PoolBreaker:成功创建了PoolBreaker异常。之后,lib检查此Exception对象,但未找到任何属性。构造函数被第二次调用,但是没有传递任何参数,因此它失败了。它不是在MWE中完成的,但是如果您打印堆栈跟踪,则会看到它抱怨TypeError: __init__() missing 1 required positional argument: 'num'
  • NoPoolBreaker1:它也找不到任何属性,但是当使用零参数调用构造函数时,它不会失败,因为它具有默认值(您在输出中看到num = -9)
  • NoPoolBreaker2:由于用参数调用了super(),所以Exception具有num属性。因此,构造函数的第二次调用会收到具有正确值的必需num属性,因此它可以正常工作。
  • NoPoolBreaker3:没有调用super()并检查了NoPoolBreaker3,他发现了num属性。因此,构造函数的第二次调用会收到具有正确值的必需num属性,因此它可以正常工作。

实际返回的Exception总是第一个,似乎第二个被丢弃了。

要在进程之间传输对象,必须对它们进行腌制和复制,这由ProcessPoolExecutor完成。 可能是第二次创建异常的原因。

当然,这不是解决问题的方法,可惜我还没有找到任何人。

我同时认为,这可能是库中的错误,因为:

  • 不打算第二次创建对象,或者
  • 属性检查有误

请注意:我目前遇到的问题是,在某些程序中,NoPoolBreaker3不再起作用,并且还会导致“突然终止”异常。我不知道为什么,但这可能意味着行为取决于MWE中显​​示的更多内容。

答案 1 :(得分:0)

过去/现在是python泡菜中的错误,更多详细信息请参见:https://bugs.python.org/issue37208