繁体   English   中英

在Python中使用上下文类和contextmananger方法装饰器时的不同行为

[英]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+Cyield之后的代码-与第一个示例中的__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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM