[英]Different behaviour when using context class and contextmananger method decorator in Python
当尝试使用Python(3.5)的上下文管理器时,在以下两种情况下会出现不同的行为。 我正在尝试通过与上下文管理器结合使用正确的关闭过程来优雅地处理线程程序的KeyboardInterrupt
异常,但似乎我无法在第二种情况下正常工作,而且我不明白为什么不这样做。
两种情况的共同点是使用线程的通用“作业”任务:
import threading
class Job(threading.Thread):
def run(self):
self.active = True
while self.active:
continue
def stop(self):
self.active = False
一旦从start
(由threading.Thread
父类提供的方法run
内部run
)开始start
,就可以通过调用stop
来停止它。
我试图做到这一点的第一种方式是使用内置的__enter__
和__exit__
方法,以充分利用Python的的with
语句的支持:
class Context(object):
def __init__(self):
self.active = False
def __enter__(self):
print("Entering context")
self.job = Job()
self.job.start()
return self.job
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting context")
self.job.stop()
self.job.join()
print("Job stopped")
我使用以下代码运行它:
with Context():
while input() != "stop":
continue
等待,直到用户键入“停止”并按Enter。 如果在此循环中用户改为按Ctrl+C
创建KeyboardInterrupt
,则仍会调用__exit__
方法:
Entering context
^CExiting context
Job stopped
Traceback (most recent call last):
File "tmp2.py", line 48, in <module>
while input() != "stop":
KeyboardInterrupt
我尝试执行此操作的第二种方法是使用@contextmanager
装饰器创建一个函数:
from contextlib import contextmanager
@contextmanager
def job_context():
print("Entering context")
job = Job()
job.start()
yield job
print("Exiting context")
job.stop()
job.join()
print("Job stopped")
我再次使用with
语句运行它:
with job_context():
while input() != "stop":
continue
但是,当我运行它并按Ctrl+C
, yield
之后的代码-与第一个示例中的__exit__
方法等效的代码不会执行。 相反,Python脚本将继续在无限循环中运行。 要停止程序,我必须再次按Ctrl+C
,此时不会执行yield
之后的代码:
Entering context
^CTraceback (most recent call last):
File "tmp2.py", line 42, in <module>
while input() != "stop":
KeyboardInterrupt
^CException ignored in: <module 'threading' from '/usr/lib/python3.5/threading.py'>
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 1288, in _shutdown
t.join()
File "/usr/lib/python3.5/threading.py", line 1054, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.5/threading.py", line 1070, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
您可以在按Ctrl+C
创建中断的位置看到^C
符号。 第二种情况与第一种情况不执行等效于__exit__
的关闭代码有何不同?
根据文档 :
如果该块中发生未处理的异常,则在产生收益的位置在生成器内部将其重新引发。 因此,您可以使用
try
...except
...finally
语句来捕获错误(如果有的话),或确保进行一些清理。
您的情况如下所示:
@contextmanager
def job_context():
print("Entering context")
job = Job()
job.start()
try:
yield job
finally:
print("Exiting context")
job.stop()
job.join()
print("Job stopped")
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.