電気エネルギーの流れと設計 2025.12.24

時間, スレッド,キューに関するモジュール

Back to index page


  1. 本日の内容
  2. 時間に関するモジュール
    1. time モジュール
    2. コンピュータでデータ計測を行う場合,データの取得時間を記録することは基本中の基本です.コンピュータは必ず内部時計を持っているので,その時計を呼び出して時間を取得します.通常コンピュータは1970年1月1日午前0時からの時間をカウントしているので,それを利用します.秒単位で持っている値なので非常に大きな値であり,そのままでは使用できません.

      まずは,time モジュールを使って時間を見てみましょう.

      from time import *
      
      print(time())
      

      実行すると以下のような結果となります.

      1766448941.7837431
      

      1970年1月1日から55年と358日ですが,うるう年などもあるので,56年とちょっと経っている計算になります.小数部以下は100nsまでありますが,コンピュータの時間は10ms程度までしか当てにできません.

      さて,time モジュールですが,やはり多くの機能が含まれていますので,全部は紹介しきれません.また,ここで注意が必要なのは,上のようなサンプルプログラムを保存する際に time.py という名前を使ってはいけないことです.以前にも説明しましたが,本来のモジュール名が time.py なので自作のプログラムをその名前にしてしまうと呼び出し矛盾が生じてエラーになります.

    3. 経過時間
    4. では,time() 関数を使用して経過時間を知るにはどうすればよいかというと,計測開始時に一度時間を取得し,データが得られた時間に再度時間を取得して引き算することで求められます.

      4桁の素数を全部探すプログラムを作って,その計算時間を測定してみましょう.

      from time import *
      
      start = time()
      
      for i in range(1000, 9999):
          for j in range(2, i):
              if i % j == 0:
                  break
          else:
              print(i, end = ' ')
      else:
          print()
      
      now = time() - start
      print(f'Elapsed time: {now}')
      

      結果は以下のようになりました.

      1009 1013 1019 1021 1031 1033 1039 1049 1051 1061 1063 1069 1087 1091 1093 
      1097 1103 1109 1117 1123 1129 1151 1153 1163 1171 1181 1187 1193 1201 1213 
      1217 1223 1229 1231 1237 1249 1259 1277 1279 1283 1289 1291 1297 1301 1303 
      1307 1319 1321 1327 1361 1367 1373 1381 1399 1409 1423 1427 1429 1433 1439 
      
                                         中略
      
      9661 9677 9679 9689 9697 9719 9721 9733 9739 9743 9749 9767 9769 9781 9787 
      9791 9803 9811 9817 9829 9833 9839 9851 9857 9859 9871 9883 9887 9901 9907 
      9923 9929 9931 9941 9949 9967 9973
      Elapsed time: 153.581280708313
      

      ということで,私の環境では153秒かかりました.

      IDLEはインタラクティブに作業するところが重要なのですが,そのせいで動作は遅くなります.Windows に標準で備わっているコマンドプロンプトを使用すると,なんと 0.34 秒で終わります!
    5. datetime モジュール
    6. 秒数だけでいろんなことを表すのは不便ですので,我々が普段馴染んでいる「時刻」で表示できる datetime モジュールも用意されています.ただし,扱いにはこちらも注意が必要です.まずは,現在時刻を取得してみましょう.

      from datetime import *
      
      print(time())
      

      結果ですが,以下のようになり,ちゃんと時刻を表示してくれませんでした.

      00:00:00
      

      以下のように一部を変更します.

      from datetime import *
      
      print(datetime.now())
      

      するとちゃんと時刻を表示してくれました.

      2025-12-23 09:48:43.991163
      

      いくつかややこしい点があります.datetime モジュールの中の datetime メソッドを呼び出すだけではダメで,それに now モジュールも作用させなければなりません.

    7. datetime の使い方
    8. 実験データを計測中に自動で保存する設定にした場合,保存するファイル名を毎回変更しないと,貴重なデータを上書きで消してしまうことになってしまいます.そのたびにファイル保存ダイアローグを表示して自分でファイル名を入力するのは面倒なので,現在時刻を取得してそれをファイル名に入れることで上書き防止を行うことがよく行われます.俗にタイムスタンプといいます.

      ただ,秒数が上の例のように長々と続くのは分かりづらいので,年月日と時刻を整形して付け加えるのが便利です.その際に datetime が返す時刻オブジェクトを文字列に変換してくれる strftime メソッドを使用します.これまで紹介していなかった %記法を使用しますが,以下の例に示すように単純なので,すぐに理解できると思います.

      from datetime import *
      
      date = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
      
      print(date)
      

      2025-12-23-09-59-23

      記号の意味は分かると思いますが,Y が年,m が月,d は日,H が時間で M が分,S が秒です.時刻関係が大文字で規定されているので,日付に関する方は文字が重複すると小文字になります.

      測定に1秒以上かかる場合は上の書式で十分ですが,1秒以下で終了する測定を自動で続ける場合にはマイクロ秒も追加しなければいけません.その際には %f をつけます.
    9. sleep 関数
    10. 一定の時間間隔でデータを取得したい場合には sleep 関数を使用します.引数に秒単位の値を指定します.

      from time import *
      from random import *
      
      for _ in range(10):
          print(randint(10, 99))
      
          sleep(0.5)
      

      上のプログラムは2桁の正の整数を乱数により10回発生させますが,0.5 秒間隔で表示します.

      演習問題

      1. 上のプログラムを改造します.経過時間と乱数の両方を表示させましょう.時間の単位は秒にし,小数第2位まで表示させます.
      2. 0.00, 43
        0.51, 98
        1.02, 94
        1.53, 42
        2.04, 40
        2.55, 65
        3.06, 93
        3.57, 89
        4.08, 44
        4.59, 85
        

        解答例

  3. スレッド
  4. スレッドという言葉自体は最近のソーシャルメディアなどで関連する話題の一連の流れを表す言葉としてすでに日常的に使用されています.プログラミングにおいても同じような意味なのですが,それは複数の処理を並列して行う際に,それぞれの関連ある処理を指してスレッドと言います.2つ以上の関数を同時に動作させるときに使用します.

    スレッドもモジュールを読み込んで実行しますが,モジュール名は threading とちょっと変えてあるので,それも注意してください.

    今回使用するのはカンマ区切りのデータで引用符でくくらない形式とします.

    次に示すプログラムは1秒に1回3桁の乱数を発生させる処理と,3秒に1回4桁の乱数を発生させることを並列に行うものです.

    from time import *
    from random import *
    from threading import *
    
    def digits_3():
        for _ in range(10):
            sleep(1)
            print(randint(100, 999))
    
    def digits_4():
        for _ in range(5):
            sleep(3)
            print(randint(1000, 9999))
    
    thread1 = Thread(target = digits_3)
    thread2 = Thread(target = digits_4)
    
    thread1.start()
    thread2.start()
    
    thread1.join()
    thread2.join()
    

    実行すると画面には以下のように乱数が表示されます.

    429
    406
    3721
    743
    392
    961
    2024
    628
    893
    763
    5655
    267
    612
    5152
    1221
    

    動作的には次の図のようになっています.

  5. キュー
    1. キューの基本
    2. カタカナでキューと書くとテレビ番組の撮影などで開始の合図を送るときの表現のように思われますが,元の綴りは queue でこれは待ち行列のことです.混んでいるお店で注文するために並んでいる人の列を queue と言います.

      プログラミングでは処理を待つ間列に並んでいるようなイメージです.特に何も指定しなければ「先入れ先出し」で,キューに入った順に処理されます.スレッドを使用すると複数の処理が同時に動いていますので,その動作によってなされた処理が順番に並んでいるような状態で,データを安全にやりとりするためのメソッドです.queue クラスをインポートして使用します.

      from random import *
      from queue import *
      
      data_queue = Queue()
      
      data_queue.put(randint(10, 99))
      
      value = data_queue.get()
      
      print(value)
      

      上のようにとりあえずキューを作成し,put でキューに値を追加し,get で値を取り出すのが基本です.

    3. スレッドとキューの組み合わせ
    4. 以下の動作を行うプログラムを考えます.

      1. 不定期に2桁の乱数を20個発生させる(スレッド1)
      2. 不定期に3桁の乱数を20個発生させる(スレッド2)
      3. 発生させた乱数をその順番に発生した時間と一緒に表示する(スレッド3)
      4. CSVファイルにデータを保存する

      from random import *
      from queue import *
      from time import *
      from threading import *
      from csv import *
      
      def value3():
          for _ in range(20):
              sleep(1 / randint(2, 5))
              t = time() - start
              value = randint(10, 99)
              data_queue.put((t, value))
              print(f'{t:.2f}, {value}')
      
      def value4():
          for _ in range(20):
              sleep(1 / randint(2, 5))
              t = time() - start
              value = randint(100, 999)
              data_queue.put((t, value))
              print(f'{t:.2f}, {value}')
      
      def data_write():
          tt = 1
          while tt <= 20:
              d1, d2 = data_queue.get()
      
              with open('random_num.csv', 'a', newline = '') as f:
                  write_data = writer(f)
                  write_data.writerow([d1, d2])
      
              tt += 1
      
      start = time()
      
      data_queue = Queue()
      
      thread1 = Thread(target = value3)
      thread2 = Thread(target = value4)
      thread3 = Thread(target = data_write)
      
      thread1.start()
      thread2.start()
      thread3.start()
      
      thread1.join()
      thread2.join()
      thread3.join()
      

      実行すると以下のように乱数が表示され,同時にファイルがフォルダに保存されています.

      0.25, 50
      0.33, 375
      0.59, 5620.59, 44
      
      1.14, 6351.14, 90
      
      1.35, 675
      1.40, 21
      1.60, 756
      1.74, 24
      1.85, 274
      2.03, 23
      2.10, 596
      2.37, 90
      2.40, 728
      2.60, 73
      2.85, 65
      2.91, 487
      3.10, 69
      3.16, 765
      3.50, 461
      3.66, 63
      3.74, 131
      4.00, 33
      4.14, 282
      4.39, 65
      4.63, 29
      4.68, 405
      4.88, 674.88, 303
      
      5.20, 16
      5.45, 377
      5.54, 26
      5.79, 401
      5.92, 85
      6.16, 236.17, 807
      
      6.41, 113
      6.46, 96
      6.79, 687
      

      画面上ではデータの表示が崩れているところがありますが,保存されているデータは問題ありません.

      演習問題

      1. タイムスタンプ追加
      2. 上のプログラムでは保存するCSVファイルのファイル名を random_num.csv と決め打ちしていましたが,実行するたびに上書きしないようにタイムスタンプを追加してみましょう.ただし,ここで注意が必要です.time モジュールと datetime モジュールには両方とも time() というメソッドがあるため,これまで import 文で使用してきたアスタリスク * が使えません.面倒でも以下のようにモジュール名.メソッドのようにしてください.

        from random import *
        from queue import *
        import time
        from threading import *
        from csv import *
        import datetime
        

        使用する関数
        
        time.time()
        time.sleep()
        datetime.datetime()
        

        ファイル名を作成する際には文字列は加算できることを使用します.

        解答例


Back to index page