针对该线程 Spinner 类提供更好的键盘中断检测

发布于 2024-10-20 20:39:09 字数 2977 浏览 1 评论 0原文

好的,我已经根据我在 Google 代码搜索中搜索到的其他一堆 Spinner 类编写了这个类。

它按预期工作,但我正在寻找更好的方法来处理 KeyboardInterrupt 和 SystemExit 异常。有更好的方法吗?

这是我的代码:

#!/usr/bin/env python
import itertools
import sys
import threading

class Spinner(threading.Thread):
    '''Represent a random work indicator, handled in a separate thread'''

    # Spinner glyphs
    glyphs = ('|', '/', '-', '\\', '|', '/', '-')
    # Output string format
    output_format = '%-78s%-2s'
    # Message to output while spin
    spin_message = ''
    # Message to output when done
    done_message = ''
    # Time between spins
    spin_delay = 0.1

    def __init__(self, *args, **kwargs):
        '''Spinner constructor'''
        threading.Thread.__init__(self, *args, **kwargs)
        self.daemon = True
        self.__started = False
        self.__stopped = False
        self.__glyphs = itertools.cycle(iter(self.glyphs))

    def __call__(self, func, *args, **kwargs):
        '''Convenient way to run a routine with a spinner'''
        self.init()
        skipped = False

        try:
            return func(*args, **kwargs)
        except (KeyboardInterrupt, SystemExit):
            skipped = True
        finally:
            self.stop(skipped)

    def init(self):
        '''Shows a spinner'''
        self.__started = True
        self.start()

    def run(self):
        '''Spins the spinner while do some task'''
        while not self.__stopped:
            self.spin()

    def spin(self):
        '''Spins the spinner'''
        if not self.__started:
            raise NotStarted('You must call init() first before using spin()')

        if sys.stdin.isatty():
            sys.stdout.write('\r')
            sys.stdout.write(self.output_format % (self.spin_message,
                                                   self.__glyphs.next()))
            sys.stdout.flush()
            time.sleep(self.spin_delay)

    def stop(self, skipped=None):
        '''Stops the spinner'''
        if not self.__started:
            raise NotStarted('You must call init() first before using stop()')

        self.__stopped = True
        self.__started = False

        if sys.stdin.isatty() and not skipped:
            sys.stdout.write('\b%s%s\n' % ('\b' * len(self.done_message),
                                           self.done_message))
            sys.stdout.flush()

class NotStarted(Exception):
    '''Spinner not started exception'''
    pass

if __name__ == '__main__':
    import time

    # Normal example
    spinner1 = Spinner()
    spinner1.spin_message = 'Scanning...'
    spinner1.done_message = 'DONE'
    spinner1.init()
    skipped = False

    try:
        time.sleep(5)
    except (KeyboardInterrupt, SystemExit):
        skipped = True
    finally:
        spinner1.stop(skipped)

    # Callable example
    spinner2 = Spinner()
    spinner2.spin_message = 'Scanning...'
    spinner2.done_message = 'DONE'
    spinner2(time.sleep, 5)

提前谢谢您。

Ok, I've wrote this class based in a bunch of others Spinner classes that I've googled in Google Code Search.

It's working as intended, but I'm looking for a better way to handle KeyboardInterrupt and SystemExit exceptions. Is there better approaches?

Here's my code:

#!/usr/bin/env python
import itertools
import sys
import threading

class Spinner(threading.Thread):
    '''Represent a random work indicator, handled in a separate thread'''

    # Spinner glyphs
    glyphs = ('|', '/', '-', '\\', '|', '/', '-')
    # Output string format
    output_format = '%-78s%-2s'
    # Message to output while spin
    spin_message = ''
    # Message to output when done
    done_message = ''
    # Time between spins
    spin_delay = 0.1

    def __init__(self, *args, **kwargs):
        '''Spinner constructor'''
        threading.Thread.__init__(self, *args, **kwargs)
        self.daemon = True
        self.__started = False
        self.__stopped = False
        self.__glyphs = itertools.cycle(iter(self.glyphs))

    def __call__(self, func, *args, **kwargs):
        '''Convenient way to run a routine with a spinner'''
        self.init()
        skipped = False

        try:
            return func(*args, **kwargs)
        except (KeyboardInterrupt, SystemExit):
            skipped = True
        finally:
            self.stop(skipped)

    def init(self):
        '''Shows a spinner'''
        self.__started = True
        self.start()

    def run(self):
        '''Spins the spinner while do some task'''
        while not self.__stopped:
            self.spin()

    def spin(self):
        '''Spins the spinner'''
        if not self.__started:
            raise NotStarted('You must call init() first before using spin()')

        if sys.stdin.isatty():
            sys.stdout.write('\r')
            sys.stdout.write(self.output_format % (self.spin_message,
                                                   self.__glyphs.next()))
            sys.stdout.flush()
            time.sleep(self.spin_delay)

    def stop(self, skipped=None):
        '''Stops the spinner'''
        if not self.__started:
            raise NotStarted('You must call init() first before using stop()')

        self.__stopped = True
        self.__started = False

        if sys.stdin.isatty() and not skipped:
            sys.stdout.write('\b%s%s\n' % ('\b' * len(self.done_message),
                                           self.done_message))
            sys.stdout.flush()

class NotStarted(Exception):
    '''Spinner not started exception'''
    pass

if __name__ == '__main__':
    import time

    # Normal example
    spinner1 = Spinner()
    spinner1.spin_message = 'Scanning...'
    spinner1.done_message = 'DONE'
    spinner1.init()
    skipped = False

    try:
        time.sleep(5)
    except (KeyboardInterrupt, SystemExit):
        skipped = True
    finally:
        spinner1.stop(skipped)

    # Callable example
    spinner2 = Spinner()
    spinner2.spin_message = 'Scanning...'
    spinner2.done_message = 'DONE'
    spinner2(time.sleep, 5)

Thank you in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

旧城空念 2024-10-27 20:39:09

您可能不需要担心捕获 SystemExit< /a> 因为它是由 sys.exit() 引发的。您可能想在程序退出之前捕获它以清理一些资源。

捕获 KeyboardInterrupt 的另一种方法是注册一个信号处理程序来捕获SIGINT。然而,对于您的示例,使用 try.. except 更有意义,因此您走在正确的轨道上。

一些小建议:

  • 也许将 __call__ 方法重命名为 start,以便更清楚地表明您正在开始一项工作。
  • 您可能还希望通过在 start 方法中(而不是在构造函数中)附加新线程来使 Spinner 类可重用。
  • 还要考虑当用户针对当前微调器作业按下 CTRL-C 时会发生什么 - 是否可以启动下一个作业,或者应用程序应该退出?
  • 您还可以将 spin_message 作为 start 的第一个参数,以将其与即将运行的任务关联起来。

例如,以下是某人使用 Spinner 的方式:

dbproc = MyDatabaseProc()
spinner = Spinner()
spinner.done_message = 'OK'
try:
    spinner.start("Dropping the database", dbproc.drop, "mydb")
    spinner.start("Re-creating the database", dbproc.create, "mydb")
    spinner.start("Inserting data into tables", dbproc.populate)
    ...
except (KeyboardInterrupt, SystemExit):
    # stop the currently executing job
    spinner.stop()
    # do some cleanup if needed..
    dbproc.cleanup()

You probably don't need to worry about catching SystemExit as it is raised by sys.exit(). You might want to catch it to clean up some resources just before your program exits.

The other way to catch KeyboardInterrupt is to register a signal handler to catch SIGINT. However for your example using try..except makes more sense, so you're on the right track.

A few minor suggestions:

  • Perhaps rename the __call__ method to start, to make it more clear you're starting a job.
  • You might also want to make the Spinner class reusable by attaching a new thread within the start method, rather than in the constructor.
  • Also consider what happens when the user hits CTRL-C for the current spinner job -- can the next job be started, or should the app just exit?
  • You could also make the spin_message the first argument to start to associate it with the task about to be run.

For example, here is how someone might use Spinner:

dbproc = MyDatabaseProc()
spinner = Spinner()
spinner.done_message = 'OK'
try:
    spinner.start("Dropping the database", dbproc.drop, "mydb")
    spinner.start("Re-creating the database", dbproc.create, "mydb")
    spinner.start("Inserting data into tables", dbproc.populate)
    ...
except (KeyboardInterrupt, SystemExit):
    # stop the currently executing job
    spinner.stop()
    # do some cleanup if needed..
    dbproc.cleanup()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文