Python sched.scheduler превышает максимальную глубину рекурсии

Недавно я начал изучать Python, и часть простого приложения, которое я делаю, включает таймер с отображением в формате чч:мм:сс, работающий в отдельном потоке.

Просматривая Интернет, я нашел два способа реализовать это:

  1. Использование расписания.scheduler
  2. Использование threading.Timer

То, как я это сделал, выглядит одинаково для обеих реализаций:

запланировано:

def tick(self, display, alarm_time):

    # Schedule this function to run every minute
    s = sched.scheduler(time.time, time.sleep)
    s.enter(1, 1, self.tick, ([display, alarm_time]))

    # Update the time
    self.updateTime(display)

Таймер:

def tick(self, display):

    # Schedule this function to run every second
    t = Timer(1, self.tick, (display,alarm_time))
    t.start()

    # Update the time
    self.updateTime(display)
  1. Работает нормально в отношении правильной пометки, но через несколько минут генерирует следующую ошибку: RuntimeError: превышена максимальная глубина рекурсии. Я знаю, что вы можете увеличить максимальный уровень рекурсии вручную, но ведь в этом нет необходимости?

  2. Ошибки нет, но иногда секунды пропускаются или тикают неравномерно.

Может кто-нибудь указать мне в правильном направлении, как это сделать правильно? Спасибо.


person user171331    schedule 10.09.2009    source источник
comment
Пожалуйста, разместите ссылку, которую вы нашли для использования threading.Timer, как это.   -  person S.Lott    schedule 10.09.2009
comment
Название вашего вопроса совершенно неверно. У вас есть таймер, превышающий глубину рекурсии. Не планировщик.   -  person S.Lott    schedule 10.09.2009
comment
Извините, если я был не ясен. Отредактированный вопрос. Предложение использовать таймер пришло отсюда ‹docs.python.org/library/sched.html› Пожалуйста, имейте в виду, что я всего лишь новичок и, возможно, у меня неправильное представление.   -  person user171331    schedule 10.09.2009


Ответы (2)


Вот как можно превратить одноразовое событие в периодическое событие, например. с sched: если функция должна создать собственный планировщик и быть единственной вещью, работающей в своем потоке:

def tick(self, display, alarm_time, scheduler=None):
  # make a new scheduler only once & schedule this function immediately
  if scheduler is None:
    scheduler = sched.scheduler(time.time, time.sleep)
    scheduler.enter(0, 1, self.tick, ([display, alarm_time, scheduler]))
    scheduler.run()

  # reschedule this function to run again in a minute
  scheduler.enter(1, 1, self.tick, (display, alarm_time, scheduler]))

  # do whatever actual work this function requires, e.g.:
  self.updateTime(display)

Если другие события также должны быть запланированы в том же потоке, тогда планировщик должен быть создан и принадлежать «где-то еще» — часть if выше может быть реорганизована в другой метод, например:

def scheduleperiodic(self, method, *args):
  self.scheduler = sched.scheduler(time.time, time.sleep)
  self.scheduler.enter(0, 1, method, args)
  # whatever else needs to be scheduled at start, if any, can go here
  self.scheduler.run()

def tick(self, display, alarm_time):
  # reschedule this function to run again in a minute
  self.scheduler.enter(60, 1, self.tick, (display, alarm_time))

  # do whatever actual work this function requires, e.g.:
  self.updateTime(display)

Опять же, конечно, и как всегда с sched, пока планировщик работает, он (и запланированные обратные вызовы событий) «примет на себя» рассматриваемый поток (поэтому вам нужно будет выделить для него отдельный поток, если вам нужно другие вещи, которые должны происходить одновременно).

Если вам нужно использовать такую ​​идиому во многих функциях, ее можно преобразовать в декоратор, но это несколько скроет лежащую в основе простоту идиомы, поэтому я предпочитаю это простое, открытое использование. Кстати, обратите внимание, что time.time и time.sleep используют секунды, а не минуты в качестве единицы времени, поэтому вам нужно 60, а не один, чтобы указать «через минуту» ;-).

person Alex Martelli    schedule 10.09.2009
comment
Я сохранил предложение для декоратора - person DrFalk3n; 11.09.2009

Таймер — это одноразовое событие. Нельзя зацикливаться таким образом.

Использование Таймера для вызова функции, которая затем создает другой Таймер, который вызывает функцию, которая создает Таймер, который вызывает функцию, которая создает Таймер, ..., должен достичь предела рекурсии.

Вы не упоминаете свою ОС, но «пропуск» или «нерегулярное тиканье» происходит по двум причинам.

  1. Ваш компьютер занят, а «1 секунда» означает «почти 1 секунду, в зависимости от того, что еще происходит».

  2. Если вы запустите таймер с 0,9999 секунды и подождите 1 секунду, вы можете получить 1,9999 (округляя до 1) или 2,00000. Может показаться, что время дублируется или пропускается. Внутренние аппаратные часы вашего компьютера очень точны, и округление до ближайшей секунды (всегда) приведет к отдаленной возможности дублирования или пропусков.

Используйте расписание правильно. http://docs.python.org/library/sched.html#module-sched

Ваш фрагмент кода также не имеет смысла для sched. Вам не нужно создавать новый объект планировщика. Вам нужно только создать новое событие.

Прочтите http://docs.python.org/library/sched.html#sched.scheduler.enter при создании нового события для существующего экземпляра планировщика.

person S.Lott    schedule 10.09.2009
comment
Спасибо за Ваш ответ. Я уточнил вопрос, включив в него запланированную реализацию и ошибку, но я предполагаю, что причина та же, что вы указали для Timer. Однако, если sched просто планирует одно событие на более позднюю дату, я все еще не вижу, как избежать ограничения рекурсии :( - person user171331; 10.09.2009
comment
Помимо ссылок на страницы документации по Python, в каком направлении двигаться дальше :) - person HongboZhu; 12.02.2014
comment
Является ли вызов threading.Timer() рекурсией? Или функция возвращается после вызова Timer? - person HongboZhu; 12.02.2014
comment
Нет. Этот ответ начинается совершенно неправильно. Я только что написал небольшой скрипт, который ничего не делает, кроме как создает бесконечную рекурсию, используя таймер и нулевую задержку, и он работает нормально уже несколько минут. - person Tarnay Kálmán; 17.08.2014