![](/img/trans.png)
[英]Python warnings filter not catching InsecurePlatformWarning
[英]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 A和Line B之間的任何地方時,我已經縮小了范圍。 讓我感到驚訝的是,警告是在output_queue.get()
附近output_queue.get()
地方發出的。 這對我來說意味着multiprocessing
使用酸洗將小部件發送給工作人員。
結果是,即使在從A 行到B行的所有內容周圍放置由warnings.catch_warnings()
創建的上下文管理器,並在此上下文中設置正確的警告過濾器也不會捕獲警告。 這對我來說意味着警告正在工作進程中發出。 將此上下文管理器放在工作代碼周圍也不會捕獲警告。
此示例省略了用於確定問題大小是否太小而無法分叉進程、導入多處理以及定義my_frobnal_counter
和my_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.