在 Python 中使用 context class 和 contextmananger 方法装饰器时的不同行为
Different behaviour when using context class and contextmananger method decorator in Python
在尝试使用 Python (3.5) 的上下文管理器时,我在以下两种情况下得到不同的行为。我正在尝试通过结合上下文管理器使用适当的关闭过程来优雅地处理我的线程程序的 KeyboardInterrupt
异常,但在第二种情况下我似乎无法让它工作而且我可以'不明白为什么。
这两种情况的共同点是使用线程的通用 "job" 任务:
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
父 class 提供的方法,内部调用 run
),可以通过调用 [=23] 停止=].
我尝试这样做的第一种方法是使用内置的 __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
这会一直等到用户键入 "stop" 并按回车键。如果在此循环期间用户改为按 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__
的关闭代码?
If an unhandled exception occurs in the block, it is reraised inside
the generator at the point where the yield occurred. Thus, you can use
a try
...except
...finally
statement to trap the error (if any), or
ensure that some cleanup takes place.
在你的情况下,这看起来像:
@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")
在尝试使用 Python (3.5) 的上下文管理器时,我在以下两种情况下得到不同的行为。我正在尝试通过结合上下文管理器使用适当的关闭过程来优雅地处理我的线程程序的 KeyboardInterrupt
异常,但在第二种情况下我似乎无法让它工作而且我可以'不明白为什么。
这两种情况的共同点是使用线程的通用 "job" 任务:
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
父 class 提供的方法,内部调用 run
),可以通过调用 [=23] 停止=].
我尝试这样做的第一种方法是使用内置的 __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
这会一直等到用户键入 "stop" 并按回车键。如果在此循环期间用户改为按 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__
的关闭代码?
If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. Thus, you can use a
try
...except
...finally
statement to trap the error (if any), or ensure that some cleanup takes place.
在你的情况下,这看起来像:
@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")