Python sleep():コードに時間遅延を追加する方法

Python sleep():コードに時間遅延を追加する方法

Pythonプログラムに何かを待たせる必要がありましたか? ほとんどの場合、できるだけ早くコードを実行する必要があります。 しかし、実際にコードをしばらくスリープさせることが実際にあなたの最大の関心事である場合があります。

たとえば、Pythonのsleep()呼び出しを使用して、プログラムの遅延をシミュレートできます。 おそらく、ファイルがアップロードまたはダウンロードされるのを待つか、グラフィックがロードされるか、画面に描画されるのを待つ必要があります。 Web APIの呼び出し間、またはデータベースへのクエリ間で一時停止する必要さえあります。 Pythonsleep()呼び出しをプログラムに追加すると、これらの各ケースで役立つ可能性があります。

このチュートリアルでは、次の方法でPythonsleep()呼び出しを追加する方法を学習します。

  • time.sleep()

  • デコレータ

  • スレッド

  • 非同期IO

  • グラフィカルユーザーインタフェース

この記事は、Pythonの知識を増やしたいと考えている中間開発者を対象としています。 それがあなたのように思えたら、始めましょう!

Free Bonus:Get our free "The Power of Python Decorators" guideは、よりクリーンでよりPythonicなプログラムに書き込むために使用できる3つの高度なデコレータパターンとテクニックを示しています。

time.sleep()でPythonsleep()呼び出しを追加する

Pythonには、プログラムをスリープ状態にするためのサポートが組み込まれています。 timeモジュールには関数sleep()があり、これを使用して、指定した秒数の間、呼び出し元のスレッドの実行を一時停止できます。

time.sleep()の使用方法の例を次に示します。

>>>

>>> import time
>>> time.sleep(3) # Sleep for 3 seconds

コンソールでこのコードを実行する場合、REPLに新しいステートメントを入力する前に遅延が発生するはずです。

Note: Python 3.5では、コア開発者がtime.sleep()の動作をわずかに変更しました。 新しいPythonsleep()システムコールは、スリープがシグナルによって中断された場合でも、少なくとも指定した秒数だけ持続します。 ただし、信号自体が例外を発生させる場合、これは適用されません。

Pythonのtimeitモジュールを使用して、スリープがどのくらい続くかをテストできます。

$ python3 -m timeit -n 3 "import time; time.sleep(3)"
3 loops, best of 3: 3 sec per loop

ここでは、-nパラメータを使用してtimeitモジュールを実行します。これにより、次のステートメントを実行する回数がtimeitに通知されます。 timeitがステートメントを3回実行し、最適な実行時間は3秒であったことがわかります。これは、予想どおりです。

timeitがコードを実行するデフォルトの回数は100万回です。 上記のコードをデフォルトの-nで実行すると、反復ごとに3秒で、端末が約34日間ハングします。 timeitモジュールには、documentationでチェックアウトできる他のいくつかのコマンドラインオプションがあります。

もう少し現実的なものを作成しましょう。 システム管理者は、Webサイトの1つがいつダウンしたかを知る必要があります。 ウェブサイトのステータスコードを定期的に確認できるようにしたいが、ウェブサーバーに常に問い合わせることはできません。そうしないと、パフォーマンスに影響します。 このチェックを行う1つの方法は、Pythonsleep()システムコールを使用することです。

import time
import urllib.request
import urllib.error

def uptime_bot(url):
    while True:
        try:
            conn = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            # Email admin / log
            print(f'HTTPError: {e.code} for {url}')
        except urllib.error.URLError as e:
            # Email admin / log
            print(f'URLError: {e.code} for {url}')
        else:
            # Website is up
            print(f'{url} is up')
        time.sleep(60)

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

ここでは、引数としてURLを受け取るuptime_bot()を作成します。 次に、関数はそのURLをurllibで開こうとします。 HTTPErrorまたはURLErrorがある場合、プログラムはそれをキャッチしてエラーを出力します。 (ライブ環境では、エラーをログに記録し、おそらくウェブマスターまたはシステム管理者にメールを送信します。)

エラーが発生しない場合、コードはすべて正常であることを出力します。 何が起こっても、プログラムは60秒間スリープします。 これは、1分に1回だけWebサイトにアクセスすることを意味します。 この例で使用されているURLは不適切であるため、1分ごとに次の内容がコンソールに出力されます。

HTTPError: 404 for http://www.google.com/py

先に進み、http://www.google.comなどの既知の適切なURLを使用するようにコードを更新します。 その後、再実行して正常に動作することを確認できます。 また、コードを更新してメールを送信したり、エラーを記録したりすることもできます。 これを行う方法の詳細については、Sending Emails With PythonLogging in Pythonを確認してください。

デコレータを使用したPythonsleep()呼び出しの追加

失敗した機能を再試行する必要がある場合があります。 これの一般的な使用例の1つは、サーバーがビジーであったためにファイルのダウンロードを再試行する必要がある場合です。 通常、サーバーに頻繁にリクエストを送信することは望ましくないため、各リクエストの間にPythonsleep()呼び出しを追加することをお勧めします。

私が個人的に経験したもう1つのユースケースは、自動テスト中にユーザーインターフェースの状態を確認する必要がある場合です。 ユーザーインターフェイスは、テストを実行しているコンピューターに応じて、通常よりも速くまたは遅く読み込まれる場合があります。 これにより、プログラムが何かを検証している瞬間の画面の内容が変わる可能性があります。

この場合、プログラムをしばらくスリープ状態にしてから、1〜2秒後に再確認するように指示できます。 これは、テストの合格と不合格の違いを意味します。

これらのいずれの場合でも、decoratorを使用してPythonsleep()システムコールを追加できます。 デコレータに慣れていない場合、またはデコレータをブラッシュアップしたい場合は、Primer on Python Decoratorsを確認してください。 例を見てみましょう:

import time
import urllib.request
import urllib.error

def sleep(timeout, retry=3):
    def the_real_decorator(function):
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < retry:
                try:
                    value = function(*args, **kwargs)
                    if value is None:
                        return
                except:
                    print(f'Sleeping for {timeout} seconds')
                    time.sleep(timeout)
                    retries += 1
        return wrapper
    return the_real_decorator

sleep()はデコレータです。 timeout値と、retryにする必要のある回数(デフォルトは3)を受け入れます。 sleep()の中には、装飾された関数を受け入れる別の関数the_real_decorator()があります。

最後に、最も内側の関数wrapper()は、装飾された関数に渡す引数とキーワード引数を受け入れます。 これは魔法が起こる場所です! whileループを使用して、関数の呼び出しを再試行します。 例外がある場合は、time.sleep()を呼び出し、retriesカウンターをインクリメントして、関数の実行を再試行します。

ここで、uptime_bot()を書き直して、新しいデコレータを使用します。

@sleep(3)
def uptime_bot(url):
    try:
        conn = urllib.request.urlopen(url)
    except urllib.error.HTTPError as e:
        # Email admin / log
        print(f'HTTPError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.HTTPError
    except urllib.error.URLError as e:
        # Email admin / log
        print(f'URLError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.URLError
    else:
        # Website is up
        print(f'{url} is up')

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

ここでは、sleep()を3秒のsleep()で装飾します。 また、元のwhileループと、sleep(60)への古い呼び出しも削除しました。 デコレータがこれを処理します。

行ったもう1つの変更は、例外処理ブロック内にraiseを追加することです。 これは、デコレータが適切に機能するようにするためです。 これらのエラーを処理するデコレータを記述することもできますが、これらの例外はurllibにのみ適用されるため、デコレータをそのままにしておく方がよい場合があります。 そうすれば、より広範な機能で動作します。

Note: Pythonでの例外処理をブラッシュアップしたい場合は、Python Exceptions: An Introductionを確認してください。

デコレータにいくつかの改善を加えることができます。 再試行を実行しても失敗する場合は、最後のエラーを再発生させることができます。 また、デコレータは最後の失敗から3秒待機しますが、これは望ましくない可能性があります。 これらを練習として自由に試してみてください!

スレッドを使用したPythonsleep()呼び出しの追加

Pythonのsleep()呼び出しをthreadに追加したい場合もあります。 おそらく、実稼働中の数百万のレコードを持つデータベースに対して移行スクリプトを実行している可能性があります。 ダウンタイムを発生させたくないだけでなく、移行を完了するために必要以上に長く待機したくないため、スレッドを使用することにします。

Note:スレッドは、Pythonでconcurrencyを実行する方法です。 複数のスレッドを一度に実行して、アプリケーションのスループットを向上させることができます。 Pythonのスレッドに慣れていない場合は、An Intro to Threading in Pythonを確認してください。

お客様が何らかの減速に気付かないようにするには、各スレッドを短時間実行してからスリープする必要があります。 これを行うには2つの方法があります。

  1. 前と同じようにtime.sleep()を使用します。

  2. threadingモジュールのEvent.wait()を使用します。

time.sleep()から始めましょう。

time.sleep()の使用

PythonLogging Cookbookは、time.sleep()を使用する良い例を示しています。 Pythonのloggingモジュールはスレッドセーフであるため、この演習ではprint()ステートメントよりも少し便利です。 次のコードは、この例に基づいています。

import logging
import threading
import time

def worker(arg):
    while not arg["stop"]:
        logging.debug("worker thread checking in")
        time.sleep(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    info = {"stop": False}
    thread = threading.Thread(target=worker, args=(info,))
    thread_two = threading.Thread(target=worker, args=(info,))
    thread.start()
    thread_two.start()

    while True:
        try:
            logging.debug("Checking in from main thread")
            time.sleep(0.75)
        except KeyboardInterrupt:
            info["stop"] = True
            logging.debug('Stopping')
            break
    thread.join()
    thread_two.join()

if __name__ == "__main__":
    main()

ここでは、Pythonのthreadingモジュールを使用して2つのスレッドを作成します。 また、threadNameをstdoutに記録するロギングオブジェクトを作成します。 次に、両方のスレッドを開始し、メインスレッドから頻繁にログを記録するループを開始します。 KeyboardInterruptを使用して、ユーザーがCtrl+[.kbd .key-c]#C#を押しているのをキャッチします。

ターミナルで上記のコードを実行してみてください。 次のような出力が表示されます。

 0 Thread-1 worker thread checking in
 1 Thread-2 worker thread checking in
 1 MainThread Checking in from main thread
752 MainThread Checking in from main thread
1001 Thread-1 worker thread checking in
1001 Thread-2 worker thread checking in
1502 MainThread Checking in from main thread
2003 Thread-1 worker thread checking in
2003 Thread-2 worker thread checking in
2253 MainThread Checking in from main thread
3005 Thread-1 worker thread checking in
3005 MainThread Checking in from main thread
3005 Thread-2 worker thread checking in

各スレッドが実行されてからスリープ状態になると、ロギング出力がコンソールに出力されます。 例を試したので、これらの概念を独自のコードで使用できるようになります。

Event.wait()の使用

threadingモジュールは、time.sleep()のように使用できるEvent()を提供します。 ただし、Event()には、応答性が高いという追加の利点があります。 これは、イベントが設定されると、プログラムがすぐにループから抜け出すためです。 time.sleep()を使用すると、スレッドが終了する前に、コードはPythonsleep()呼び出しが終了するのを待つ必要があります。

ここでwait()を使用する理由は、wait()non-blockingであるのに対し、time.sleep()blockingであるためです。 これが意味するのは、time.sleep()を使用すると、sleep()呼び出しが終了するのを待つ間、メインスレッドが実行を継続するのをブロックするということです。 wait()はこの問題を解決します。 これらすべてがPythonのthreading documentationでどのように機能するかについて詳しく読むことができます。

Event.wait()を使用してPythonsleep()呼び出しを追加する方法は次のとおりです。

import logging
import threading

def worker(event):
    while not event.isSet():
        logging.debug("worker thread checking in")
        event.wait(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    event = threading.Event()

    thread = threading.Thread(target=worker, args=(event,))
    thread_two = threading.Thread(target=worker, args=(event,))
    thread.start()
    thread_two.start()

    while not event.isSet():
        try:
            logging.debug("Checking in from main thread")
            event.wait(0.75)
        except KeyboardInterrupt:
            event.set()
            break

if __name__ == "__main__":
    main()

この例では、threading.Event()を作成し、それをworker()に渡します。 (前の例では、代わりに辞書を渡したことを思い出してください。)次に、ループを設定して、eventが設定されているかどうかを確認します。 そうでない場合、コードはメッセージを出力し、しばらく待ってから再度確認します。 イベントを設定するには、[。keys]#Ctrl [.kbd .key-c]#C ##を押します。 イベントが設定されると、 `+ worker()`が戻り、ループが中断され、プログラムが終了します。

Note:辞書について詳しく知りたい場合は、Dictionaries in Pythonを確認してください。

上記のコードブロックを詳しく見てください。 各ワーカースレッドに異なるスリープ時間をどのように渡しますか? 理解できますか? このエクササイズに自由に取り組んでください。

非同期IOを使用したPythonsleep()呼び出しの追加

非同期機能は3.4リリースでPythonに追加され、それ以降、この機能セットは積極的に拡張されています。 Asynchronous programmingは、複数のタスクを一度に実行できる並列プログラミングの一種です。 タスクが終了すると、メインスレッドに通知します。

asyncioは、Pythonsleep()呼び出しを非同期で追加できるようにするモジュールです。 Pythonによる非同期プログラミングの実装に慣れていない場合は、Async IO in Python: A Complete WalkthroughPython Concurrency & Parallel Programmingを確認してください。

Python独自のdocumentationの例を次に示します。

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Python 3.7+
asyncio.run(main())

この例では、main()を実行し、2つのprint()呼び出しの間に1秒間スリープさせます。

asyncioドキュメントのCoroutines and Tasks部分からのより説得力のある例を次に示します。

import asyncio
import time

async def output(sleep, text):
    await asyncio.sleep(sleep)
    print(text)

async def main():
    print(f"Started: {time.strftime('%X')}")
    await output(1, 'First')
    await output(2, 'Second')
    await output(3, 'Third')
    print(f"Ended: {time.strftime('%X')}")

# Python 3.7+
asyncio.run(main())

このコードでは、sleeptextが出力されるまでの秒数にかかるoutput()というワーカーを作成します。 次に、Pythonのawaitキーワードを使用して、output()コードが実行されるのを待ちます。 output()async関数としてマークされているため、ここではawaitが必要です。したがって、通常の関数のように呼び出すことはできません。

このコードを実行すると、プログラムはawaitを3回実行します。 コードは、1、2、および3秒間待機し、合計で6秒間待機します。 タスクを並行して実行するようにコードを書き換えることもできます。

import asyncio
import time

async def output(text, sleep):
    while sleep > 0:
        await asyncio.sleep(1)
        print(f'{text} counter: {sleep} seconds')
        sleep -= 1

async def main():
    task_1 = asyncio.create_task(output('First', 1))
    task_2 = asyncio.create_task(output('Second', 2))
    task_3 = asyncio.create_task(output('Third', 3))
    print(f"Started: {time.strftime('%X')}")
    await task_1
    await task_2
    await task_3
    print(f"Ended: {time.strftime('%X')}")

if __name__ == '__main__':
    asyncio.run(main())

これで、create_task()で作成できるtasksの概念を使用しています。 asyncioでタスクを使用すると、Pythonはタスクを非同期で実行します。 したがって、上記のコードを実行すると、合計6秒ではなく合計3秒で終了するはずです。

GUIを使用したPythonsleep()呼び出しの追加

Pythonsleep()呼び出しを追加する必要があるのは、コマンドラインアプリケーションだけではありません。 Graphical User Interface (GUI)を作成するときに、遅延を追加する必要がある場合があります。 たとえば、数百万のファイルをダウンロードするFTPアプリケーションを作成する場合でも、サーバーを停止させないように、バッチ間にsleep()呼び出しを追加する必要があります。

GUIコードは、event loopと呼ばれるメインスレッドですべての処理と描画を実行します。 GUIコード内でtime.sleep()を使用すると、そのイベントループがブロックされます。 ユーザーの観点からすると、アプリケーションがフリーズしたように見える場合があります。 このメソッドを使用してスリープしている間、ユーザーはアプリケーションを操作できません。 (Windowsでは、アプリケーションが現在どのように応答しないかについてのアラートを受け取ることもあります。)

幸い、time.sleep()以外にも使用できる方法があります。 次のいくつかのセクションでは、TkinterとwxPythonの両方でPythonsleep()呼び出しを追加する方法を学習します。

Tkinterで寝ている

tkinterはPython標準ライブラリの一部です。 LinuxまたはMacでプリインストールされたバージョンのPythonを使用している場合、利用できない場合があります。 ImportErrorを取得した場合は、それをシステムに追加する方法を調べる必要があります。 ただし、install Python yourselfの場合、tkinterはすでに使用可能になっているはずです。

まず、time.sleep()を使用する例を見ていきます。 このコードを実行して、Pythonsleep()呼び出しを間違った方法で追加するとどうなるかを確認します。

import tkinter
import time

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        b = tkinter.Button(text="click me", command=self.delayed)
        b.pack()

    def delayed(self):
        time.sleep(3)

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

コードを実行したら、GUIのボタンを押します。 sleep()が終了するのを待つ間、ボタンは3秒間押し下げられます。 アプリケーションに他のボタンがある場合、それらをクリックすることはできません。 また、閉じるイベントに応答できないため、スリープ中にアプリケーションを閉じることもできません。

tkinterを適切にスリープさせるには、after()を使用する必要があります。

import tkinter

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        self.root.after(3000, self.delayed)

    def delayed(self):
        print('I was delayed')

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

ここでは、幅400ピクセル、高さ400ピクセルのアプリケーションを作成します。 ウィジェットはありません。 フレームを表示するだけです。 次に、self.root.after()を呼び出します。ここで、self.rootTk()オブジェクトへの参照です。 after()は2つの引数を取ります:

  1. スリープするミリ秒数

  2. スリープが終了したときに呼び出すメソッド

この場合、アプリケーションは3秒後に文字列を標準出力に出力します。 after()time.sleep()tkinterバージョンと考えることができますが、スリープが終了した後に関数を呼び出す機能も追加されます。

この機能を使用して、ユーザーエクスペリエンスを向上させることができます。 Pythonsleep()呼び出しを追加することで、アプリケーションの読み込みが速くなったように見せ、起動後に実行時間の長いプロセスを開始できます。 そうすれば、ユーザーはアプリケーションが開くのを待つ必要がなくなります。

wxPythonで寝ている

wxPythonとTkinterには2つの大きな違いがあります。

  1. wxPythonにはさらに多くのウィジェットがあります。

  2. wxPythonは、すべてのプラットフォームでネイティブなルックアンドフィールを目指しています。

wxPythonフレームワークはPythonに含まれていないため、自分でインストールする必要があります。 wxPythonに慣れていない場合は、How to Build a Python GUI Application With wxPythonを確認してください。

wxPythonでは、wx.CallLater()を使用してPythonsleep()呼び出しを追加できます。

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        wx.CallLater(4000, self.delayed)
        self.Show()

    def delayed(self):
        print('I was delayed')

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

ここでは、wx.Frameを直接サブクラス化してから、wx.CallLater()を呼び出します。 この関数は、Tkinterのafter()と同じパラメーターを取ります。

  1. スリープするミリ秒数

  2. スリープが終了したときに呼び出すメソッド

このコードを実行すると、ウィジェットのない小さな空白のウィンドウが表示されます。 4秒後、文字列'I was delayed'がstdoutに出力されます。

wx.CallLater()を使用する利点の1つは、スレッドセーフであるということです。 スレッド内からこのメソッドを使用して、メインのwxPythonアプリケーションにある関数を呼び出すことができます。

結論

このチュートリアルでは、Pythonツールボックスに追加する価値のある新しいテクニックを習得しました! 遅延を追加してアプリケーションのペースを調整し、システムリソースの消費を防ぐ方法を知っています。 Pythonsleep()呼び出しを使用して、GUIコードをより効果的に再描画することもできます。 これにより、顧客のユーザーエクスペリエンスが大幅に向上します。

要約すると、次のツールを使用してPythonsleep()呼び出しを追加する方法を学習しました。

  • time.sleep()

  • デコレータ

  • スレッド

  • asyncio

  • キンター

  • wxPython

これで、学んだことを取り入れて、コードをスリープ状態にすることができます!