質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.79%

schedule.run_pending()の実行順序

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,302

yuudai

score 61

前提・実現したいこと

関数1と関数2を定義してどちらもscheduleで時間を設定します。先に関数2が実行されて関数2の中から関数1を実行したいです。
しかし最初のscheduleが先に実行されます。これは多分時間が若い方から実行されるからだと思います。それと関数2の中のschedule.run_pending()は実行されてないように感じました。
最後の方の
while True:
schedule.run_pending()
time.sleep(1)
の部分がschedule.every(1).minutes.do(fanc1)につながってしまっています。これをschedule.every(4).minutes.do(fanc2)の方につなげたいです。
イメージとしては最後の方の
while True:
schedule.run_pending()
time.sleep(1)

schedule.every(4).minutes.do(fanc2)
につながって次に
fanc2が実行されてその次にfanc1が実行されて、そこからは、fanc2のループでfanc1を定期的に実行すると行ったことがしたいです。
支離滅裂な文章ですが、よろしくお願いします。

発生している問題・エラーメッセージ

該当のソースコード

import schedule
import time
import sys
import datetime

sys.setrecursionlimit(10000)

def fanc1():
    print('aaaaaaa')


schedule.every(1).minutes.do(fanc1)

def fanc2():
    day_of_the_week = ["月","火","水","木","金","土","日"]

    get_today = datetime.date.today()  #今日の日付を取得
    weekday = day_of_the_week[get_today.weekday()] #曜日に変換

    if  weekday == "土" or weekday == "日":
        pass
    else:
        #機能
        while True:
            #11時50分になっていなかったら
            schedule.run_pending()
            time.sleep(1)
            #11時50分を過ぎたら
            break
#テスト用にevery(4).minutesにしているが、実際はevery().day.at("00:00")
schedule.every(4).minutes.do(fanc2)

while True:
    schedule.run_pending()
    time.sleep(1)

試したこと

ここに問題に対して試したことを記載してください。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

scheduleの使用例をみると非常にシンプルです。

import schedule
import time

schedule.every().day.at('00:00').do(lambda: print('new day has come'))

if __name__ == '__main__':
    while True:
       schedule.run_pending()
       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

import schedule
import time
import datetime


scheduler1 = schedule.Scheduler()
scheduler2 = schedule.Scheduler()


def func1():
    now = datetime.datetime.now()
    print(f'{now}: func1')


scheduler1.every(5).seconds.do(func1)


def func2():
    now = datetime.datetime.now()
    print(f'{now}: func2 invoked')
    if now.minute % 2 == 0:
        # 偶数分では何もしない
        pass
    else:
        # 奇数分では00秒~30秒の間だけfunc1をスケジュールする
        while True:
            now = datetime.datetime.now()
            if now.second % 60 < 30:
                scheduler1.run_pending()
                time.sleep(1)
            else:
                break
    print(f'{now}: func2 stopped')


scheduler2.every().minute.at(':00').do(func2)


if __name__ == '__main__':
    while True:
        scheduler2.run_pending()
        time.sleep(1)


==>

$ python3 sample.py
2019-05-11 05:01:00.033183: func2 invoked
2019-05-11 05:01:00.033183: func1
2019-05-11 05:01:05.036461: func1
2019-05-11 05:01:10.038144: func1
2019-05-11 05:01:15.040443: func1
2019-05-11 05:01:20.042950: func1
2019-05-11 05:01:25.045956: func1
2019-05-11 05:01:30.049446: func2 stopped
2019-05-11 05:02:00.065099: func2 invoked
2019-05-11 05:02:00.065099: func2 stopped
...


(確認の都合上インターバルの条件を変えています。)


ところで前提1を守りスケジューリングを入れ子にせずに書くこともできましょう。フラグを用いた状態遷移は「あまりよくない」気もしますが、そうまで複雑でないならこの実装もありな気がします。スケジューラーを入れ子にするのとフラグによる制御のどちらが望ましいか・・・意見がわかれるかも知れません。

import schedule
import datetime
import time


def seconds_of_day():
    td = datetime.datetime.now() - datetime.datetime(1, 1, 1)
    return td.seconds


is_weekday = datetime.date.today().weekday() < 5
in_time = seconds_of_day() < 11 * 3600 + 50 * 60 + 0


def func():
    if not (is_weekday and in_time):
        return
    # 何かする
    pass


schedule.every().monday.at('00:00').do(lambda: is_weekday = True)
schedule.every().saturday.at('00.00').do(lambda: is_weekday = False)
schedule.every().day.at('00:00').do(lambda: in_time = True)
schedule.every().day.at('11:50').do(lambda: in_time = False)
schedule.every().minute.do(func)

if __name__ == '__main__':
    while True:
        schedule.run_pending()
        time.sleep(1)

質問意図との関係がはっきりしませんが、

sys.setrecursionlimit(10000)

この行が気になりました。例えば再帰アルゴリズムの練習のために階乗の実装例として以下のようなものをよく見ます。これは数学的に美しい実装ではありますが、必ずしも実用的とはいえません。(言語システムの実装者にとってtail recursionの変換といった興味深いトピックではあるかも知れませんが、アプリケーションを書く立場ではこれが再帰呼び出しになるかどうか心配するよりは避けて通りたいです)

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

もしそんなに呼び出しの入れ子が深くなる心配があるなら例え10000まで許したとしてもいつその限界を突破してしまうかわかったものではないため不安になります。本件のように長期間動かし続けるプログラムならなおのことやりたくありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/12 11:46

    ありがとうございました。一つ目に挙げていただいた方法で無事に解決することができました。sys.setrecursionlimit(10000)についてはjupyter notebookでこのコードを試していた時にrecursion errorが出てしまったのでそれについて調べていたらsys.setrecursionlimit(10000)で突破できるのかなと思って一応書いていたんですけど、今ではrecursion errorは出なくなったのでその行は消しました。
    回答していただき本当にありがとうございました。

    キャンセル

  • 2019/05/12 12:09

    > recursion errorが出てしまったので
    なるほどです。
    きっとfunc2がモジュールトップのrun_pendingではなく自分自身のrun_pendingから起動されたため呼び出しスタックが深くなりすぎたのだろうと思います。最初の例のようにすればそういうことはなくなるのでスタックオーバーフロー問題も解消されたと見てよいでしょう。

    キャンセル

  • 2019/05/12 13:51

    私もそう思います。この度はありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.79%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る