scheduleの使用例をみると非常にシンプルです。
python
1import schedule
2import time
3
4schedule.every().day.at('00:00').do(lambda: print('new day has come'))
5
6if __name__ == '__main__':
7 while True:
8 schedule.run_pending()
9 time.sleep(1)
これを見たとき直感的に以下の前提をおきたくなります。
- (前提1) 各Jobの中からrun_pendingは発行しない
- (前提2) 各Jobは可能な限り短い時間で実行を終える
その前提を守って使う限りは簡単にものごとが進むと思います。しかし質問者さんは前提1を突破するようなコードを書いておられるので、可能かどうか判断するために本家サイトのドキュメント
https://schedule.readthedocs.io/en/stable/
のFAQをまず見ると・・・
https://schedule.readthedocs.io/en/stable/faq.html#how-can-i-make-sure-long-running-jobs-are-always-executed-on-time
How can I make sure long-running jobs are always executed on time?
Schedule does not account for the time it takes the job function to execute. To guarantee a stable execution schedule you need to move long-running jobs off the main-thread (where the scheduler runs).
...
とあります。どうもFAQでは前提1を突破できるか(スケジュールされたJobからさらに別のJobをスケジュールすることができるか)判断するのは難しそう。FAQの記述は「スケジューリングはメインスレッドの一か所で行う」前提で書かれているように見えるからです。
そこでさらにAPIドキュメントを見てみましたが結論からいえば前提1の突破はできると思いました。
scheduleパッケージにはSchedulerとJobというクラスがあります。それらは文字通りの意味です。schedule.~.do(func)
とする典型的な使い方ではそれらのクラスは直接見えてきませんが、Schedulerインスタンスを複数明示的に生成すればJobの入れ子が可能であると思いました。
sample.py
Python
1import schedule
2import time
3import datetime
4
5
6scheduler1 = schedule.Scheduler()
7scheduler2 = schedule.Scheduler()
8
9
10def func1():
11 now = datetime.datetime.now()
12 print(f'{now}: func1')
13
14
15scheduler1.every(5).seconds.do(func1)
16
17
18def func2():
19 now = datetime.datetime.now()
20 print(f'{now}: func2 invoked')
21 if now.minute % 2 == 0:
22 # 偶数分では何もしない
23 pass
24 else:
25 # 奇数分では00秒~30秒の間だけfunc1をスケジュールする
26 while True:
27 now = datetime.datetime.now()
28 if now.second % 60 < 30:
29 scheduler1.run_pending()
30 time.sleep(1)
31 else:
32 break
33 print(f'{now}: func2 stopped')
34
35
36scheduler2.every().minute.at(':00').do(func2)
37
38
39if __name__ == '__main__':
40 while True:
41 scheduler2.run_pending()
42 time.sleep(1)
==>
sh
1$ python3 sample.py
22019-05-11 05:01:00.033183: func2 invoked
32019-05-11 05:01:00.033183: func1
42019-05-11 05:01:05.036461: func1
52019-05-11 05:01:10.038144: func1
62019-05-11 05:01:15.040443: func1
72019-05-11 05:01:20.042950: func1
82019-05-11 05:01:25.045956: func1
92019-05-11 05:01:30.049446: func2 stopped
102019-05-11 05:02:00.065099: func2 invoked
112019-05-11 05:02:00.065099: func2 stopped
12...
(確認の都合上インターバルの条件を変えています。)
ところで前提1を守りスケジューリングを入れ子にせずに書くこともできましょう。フラグを用いた状態遷移は「あまりよくない」気もしますが、そうまで複雑でないならこの実装もありな気がします。スケジューラーを入れ子にするのとフラグによる制御のどちらが望ましいか・・・意見がわかれるかも知れません。
python
1import schedule
2import datetime
3import time
4
5
6def seconds_of_day():
7 td = datetime.datetime.now() - datetime.datetime(1, 1, 1)
8 return td.seconds
9
10
11is_weekday = datetime.date.today().weekday() < 5
12in_time = seconds_of_day() < 11 * 3600 + 50 * 60 + 0
13
14
15def func():
16 if not (is_weekday and in_time):
17 return
18 # 何かする
19 pass
20
21
22schedule.every().monday.at('00:00').do(lambda: is_weekday = True)
23schedule.every().saturday.at('00.00').do(lambda: is_weekday = False)
24schedule.every().day.at('00:00').do(lambda: in_time = True)
25schedule.every().day.at('11:50').do(lambda: in_time = False)
26schedule.every().minute.do(func)
27
28if __name__ == '__main__':
29 while True:
30 schedule.run_pending()
31 time.sleep(1)
質問意図との関係がはっきりしませんが、
sys.setrecursionlimit(10000)
この行が気になりました。例えば再帰アルゴリズムの練習のために階乗の実装例として以下のようなものをよく見ます。これは数学的に美しい実装ではありますが、必ずしも実用的とはいえません。(言語システムの実装者にとってtail recursionの変換といった興味深いトピックではあるかも知れませんが、アプリケーションを書く立場ではこれが再帰呼び出しになるかどうか心配するよりは避けて通りたいです)
python
1def factorial(n):
2 if n == 0:
3 return 1
4 else:
5 return n * factorial(n - 1)
もしそんなに呼び出しの入れ子が深くなる心配があるなら例え10000まで許したとしてもいつその限界を突破してしまうかわかったものではないため不安になります。本件のように長期間動かし続けるプログラムならなおのことやりたくありません。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/05/12 02:46
2019/05/12 03:09
2019/05/12 04:51