簡體   English   中英

Python 3:在多處理期間捕獲警告

[英]Python 3: Catching warnings during multiprocessing

太長; 沒讀

warnings.catch_warnings()上下文管理器不是線程安全的 如何在並行處理環境中使用它?

背景

下面的代碼使用 Python 的multiprocessing模塊的並行處理解決了最大化問題。 它需要一個(不可變的)小部件列表,將它們分區(參見Python 3 中大規模、蠻力最大化的高效多處理),找到所有分區的最大值(“決賽選手”),然后找到最大值(“冠軍”) )那些“決賽選手”。 如果我正確理解了自己的代碼(如果我理解了,我就不會在這里),我將與所有子進程共享內存以向它們提供輸入小部件,並且multiprocessing使用操作系統級管道和酸洗來當工作人員完成后,將決賽小部件發送回主進程。

問題的根源

我想在小部件從進程間管道中出來時發生的 unpickling 之后捕獲由小部件的重新實例化引起的冗余小部件警告 當小部件對象實例化時,它們驗證自己的數據,從 Python 標准warnings模塊warnings ,告訴應用程序的用戶小部件懷疑用戶的輸入數據有問題。 因為 unpickling 會導致對象實例化,所以我對代碼的理解意味着每個小部件對象都被重新實例化一次,當且僅當它從管道中出來后它是決賽選手——請參閱下一節以了解為什么這是不正確的.

小部件在被打斷之前就已經創建好了,因此用戶已經痛苦地意識到他輸入錯誤的內容並且不想再聽到它。 這些是我想用warnings模塊的catch_warnings()上下文管理器(即with語句)捕獲的warnings

失敗的解決方案

在我的測試中,當多余的警告被發送到我在下面標記為Line ALine B之間的任何地方時,我已經縮小了范圍。 讓我感到驚訝的是,警告是在output_queue.get()附近output_queue.get()地方發出的。 這對我來說意味着multiprocessing使用酸洗將小部件發送給工作人員。

結果是,即使在從A 行B行的所有內容周圍放置由warnings.catch_warnings()創建的上下文管理器,並在此上下文中設置正確的警告過濾器也不會捕獲警告。 這對我來說意味着警告正在工作進程中發出。 將此上下文管理器放在工作代碼周圍也不會捕獲警告。

代碼

此示例省略了用於確定問題大小是否太小而無法分叉進程、導入多處理以及定義my_frobnal_countermy_load_balancer

"Call `frobnicate(list_of_widgets)` to get the widget with the most frobnals"

def frobnicate_parallel_worker(widgets, output_queue):
    resultant_widget = max(widgets, key=my_frobnal_counter)
    output_queue.put(resultant_widget)

def frobnicate_parallel(widgets):
    output_queue = multiprocessing.Queue()
    # partitions: Generator yielding tuples of sets
    partitions = my_load_balancer(widgets)
    processes = []
    # Line A: Possible start of where the warnings are coming from.
    for partition in partitions:
        p = multiprocessing.Process(
                 target=frobnicate_parallel_worker,
                 args=(partition, output_queue))
        processes.append(p)
        p.start()
    finalists = []
    for p in processes:
        finalists.append(output_queue.get())
    # Avoid deadlocks in Unix by draining queue before joining processes
    for p in processes:
        p.join()
    # Line B: Warnings no longer possible after here.
    return max(finalists, key=my_frobnal_counter)

您可以嘗試覆蓋Process.run方法以使用warnings.catch_warnings

>>> from multiprocessing import Process
>>> 
>>> def yell(text):
...    import warnings
...    print 'about to yell %s' % text
...    warnings.warn(text)
... 
>>> class CustomProcess(Process):
...    def run(self, *args, **kwargs):
...       import warnings
...       with warnings.catch_warnings():
...          warnings.simplefilter("ignore")
...          return Process.run(self, *args, **kwargs)
... 
>>> if __name__ == '__main__':
...    quiet = CustomProcess(target=yell, args=('...not!',))
...    quiet.start()
...    quiet.join()
...    noisy = Process(target=yell, args=('AAAAAAaaa!',))
...    noisy.start()
...    noisy.join()
... 
about to yell ...not!
about to yell AAAAAAaaa!
__main__:4: UserWarning: AAAAAAaaa!
>>> 

或者你可以使用一些內部......( __warningregistry__

>>> from multiprocessing import Process
>>> import exceptions
>>> def yell(text):
...    import warnings
...    print 'about to yell %s' % text
...    warnings.warn(text)
...    # not filtered
...    warnings.warn('complimentary second warning.')
... 
>>> WARNING_TEXT = 'AAAAaaaaa!'
>>> WARNING_TYPE = exceptions.UserWarning
>>> WARNING_LINE = 4
>>> 
>>> class SelectiveProcess(Process):
...    def run(self, *args, **kwargs):
...       registry = globals().setdefault('__warningregistry__', {})
...       registry[(WARNING_TEXT, WARNING_TYPE, WARNING_LINE)] = True
...       return Process.run(self, *args, **kwargs)
... 
>>> if __name__ == '__main__':
...    p = SelectiveProcess(target=yell, args=(WARNING_TEXT,))
...    p.start()
...    p.join()
... 
about to yell AAAAaaaaa!
__main__:6: UserWarning: complimentary second warning.
>>> 

unpickling 不會導致__init__被執行兩次。 我在 Windows 上運行了以下代碼,但沒有發生(每個__init__只運行一次)。

因此,您需要向我們提供來自my_load_balancer和來自小部件類的代碼。 在這一點上,您的問題根本沒有提供足夠的信息。

作為隨機猜測,您可能會檢查my_load_balancer是否制作了小部件的副本,從而導致它們再次被實例化。

import multiprocessing
import collections

"Call `frobnicate(list_of_widgets)` to get the widget with the most frobnals"

def my_load_balancer(widgets):
    partitions = tuple(set() for _ in range(8))
    for i, widget in enumerate(widgets):
        partitions[i % 8].add(widget)
    for partition in partitions:
        yield partition

def my_frobnal_counter(widget):
    return widget.id

def frobnicate_parallel_worker(widgets, output_queue):
    resultant_widget = max(widgets, key=my_frobnal_counter)
    output_queue.put(resultant_widget)

def frobnicate_parallel(widgets):
    output_queue = multiprocessing.Queue()
    # partitions: Generator yielding tuples of sets
    partitions = my_load_balancer(widgets)
    processes = []
    # Line A: Possible start of where the warnings are coming from.
    for partition in partitions:
        p = multiprocessing.Process(
                 target=frobnicate_parallel_worker,
                 args=(partition, output_queue))
        processes.append(p)
        p.start()
    finalists = []
    for p in processes:
        finalists.append(output_queue.get())
    # Avoid deadlocks in Unix by draining queue before joining processes
    for p in processes:
        p.join()
    # Line B: Warnings no longer possible after here.
    return max(finalists, key=my_frobnal_counter)

class Widget:
    id = 0
    def __init__(self):
        print('initializing Widget {}'.format(self.id))
        self.id = Widget.id
        Widget.id += 1

    def __str__(self):
        return str(self.id)

    def __repr__(self):
        return str(self)

def main():

    widgets = [Widget() for _ in range(16)]
    result = frobnicate_parallel(widgets)
    print(result.id)


if __name__ == '__main__':
    main()

多年后,我終於有了一個解決方案(在處理一個不相關的問題時發現)。 我已經在 Python 3.7、3.8 和 3.9 上對此進行了測試。

使用空列表[]臨時修補sys.warnoptions 您只需要圍繞對process.start()的調用執行此操作。 sys.warnoptions被記錄為您不應手動修改的實現細節; 官方建議是使用warnings模塊中的函數並在os.environ設置PYTHONWARNINGS 這不起作用。 唯一似乎有效的是修補sys.warnoptions 在測試中,您可以執行以下操作:

import multiprocessing
from unittest.mock import patch
p = multiprocessing.Process(target=my_function)
with patch('sys.warnoptions', []):
    p.start()
p.join()

如果您不想使用unittest.mock ,只需手動修補:

import multiprocessing
import sys
p = multiprocessing.Process(target=my_function)
old_warnoptions = sys.warnoptions
try:
    sys.warnoptions = []
    p.start()
finally:
    sys.warnoptions = old_warnoptions
p.join()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM