Лучшее обнаружение прерываний клавиатуры для этого класса Spinner с резьбой - PullRequest
2 голосов
/ 05 марта 2011

Хорошо, я написал этот класс на основе множества других классов Spinner, которые я нашел в Google Code Search.

Он работает так, как задумано, но я ищу лучший способобрабатывать исключения 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)

Заранее спасибо.

1 Ответ

2 голосов
/ 06 марта 2011

Вам, вероятно, не нужно беспокоиться о том, чтобы поймать SystemExit, поскольку он повышен на sys.exit(). Возможно, вы захотите поймать его, чтобы очистить некоторые ресурсы непосредственно перед выходом из программы.

Другой способ перехвата KeyboardInterrupt заключается в регистрации обработчика сигнала для перехвата SIGINT. Однако для вашего примера использование try..except имеет больше смысла, поэтому вы на правильном пути.

Несколько незначительных предложений:

  • Возможно, переименуйте метод __call__ в start, чтобы было более понятно, что вы начинаете работу.
  • Возможно, вы захотите сделать класс Spinner повторно используемым, добавив новый поток в метод start, а не в конструктор.
  • Также рассмотрите, что происходит, когда пользователь нажимает 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()
...