python3 multiprocessingライブラリによる子プロセス処理内でも、pdbでデバッグする方法


問題

python3ではmultiprocessingライブラリを使うと、例えば次のように簡単に並列処理が実現できます。

test.py
from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    p = Pool(5) #数字分プロセス数を確保
    print(p.map(f, [1, 2, 3, 4, 5])) #並列処理したい関数とその引数を指定

実行すると、次のように並列処理された結果が出力されます。

$ python3 test.py 
[1, 4, 9, 16, 25]

この並列処理対象のメソッドをpdbでデバッグしたい時、あると思います。
その際にpdbを使おうと思うと、次のように書いてしまうと思います。

test_ng_pdb.py
from multiprocessing import Pool
import pdb #追加

def f(x):
    pdb.set_trace() #追加
    return x*x

if __name__ == '__main__':
    p = Pool(5) #数字分プロセス数を確保
    print(p.map(f, [1, 2, 3, 4, 5]))

しかし、次のように実行してもエラーが出てしまい、pdbによるデバッグができません。

$ python3 test_ng_pdb.py
()
The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test_ng_pdb.py", line 10, in <module>
    print(p.map(f, [1, 2, 3, 4, 5]))
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py", line 268, in map
    return self._map_async(func, iterable, mapstar, chunksize).get()
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py", line 657, in get
    raise self._value
bdb.BdbQuit

先ほどの追加部分を書かずに次のように実行したとしても、同様の問題でpdbが使えません。

$ python3 -m pdb test.py

前置きが長くなりましたが、この問題を解決してpdbでデバッグしたい、という内容になります。

解決方法

stack overflowのこの記事記載の回答を試したら上手くできました、というただの検証の記事です。。

次のように、forkした子プロセス用pdbクラスを別途定義して使います。追加でsysをインポートする必要がありますが、標準ライブラリのみで済むのがとても良いなと思っています。

test_pdb_rev.py
from multiprocessing import Pool
import sys
import pdb

#下記のclassを追記
class ForkedPdb(pdb.Pdb):
    """A Pdb subclass that may be used
    from a forked multiprocessing child
    """
    def interaction(self, *args, **kwargs):
        _stdin = sys.stdin
        try:
            sys.stdin = open('/dev/stdin')
            pdb.Pdb.interaction(self, *args, **kwargs)
        finally:
            sys.stdin = _stdin

def f(x):
    ForkedPdb().set_trace() #書き換え
    return x*x

if __name__ == '__main__':
    p = Pool(5) #数字分プロセス数を確保
    print(p.map(f, [1, 2, 3, 4, 5]))

このような感じで、pdbでデバッグできます。

$ python3 test_pdb_rev.py 
> /Users/purplejp/Documents/python/test_pdb_rev.py(20)f()
-> return x*x
(Pdb) > /Users/purplejp/Documents/python/test_pdb_rev.py(20)f()
-> return x*x
> /Users/purplejp/Documents/python/test_pdb_rev.py(20)f()
-> return x*x
(Pdb) (Pdb) > /Users/purplejp/Documents/python/test_pdb_rev.py(20)f()
-> return x*x
(Pdb) > /Users/purplejp/Documents/python/test_pdb_rev.py(20)f()
-> return x*x
(Pdb) x
1

普通にnを押して進むと、別のプロセスの変数も確認することもできました。
ということで、問題なくpdbを子プロセス上でも使うことができました。

検証環境

python 3.7.3

参考文献

Python公式ドキュメント「multiprocessing --- プロセスベースの並列処理」
Stack Overflow「How to attach debugger to a python subproccess?」