pythonでネタ進捗バーを作ってみた。


動機のお話

簡単にいうと、進捗バーを作成したくなりました。
tqdmというパッケージを使えば簡単にできるっぽいのですが、それでは面白みがありません。
せっかくアスキーアートというものがあるのだから、暇な時間はそれを映したいなと思い、いろいろ試すことにしました。

必要な知識

まず、進捗バーを表示する際に知っておくべきことがあります。
それは、表示カーソルの動かし方です。
以下のコードを見てください。

sample1.py
import time
for i in range(10):
    time.sleep(1)
    print("\r{}".format(i),end="")

これを実行すると、表示される数字が上書きされます。
注目すべきは二つ。


1. end=""

これはprint関数のオプションにある、終端文字の指定で、デフォルトは"\n"(改行文字)です。
そのため、これを""(文字なし)に変更すると、行を下に動かさずに出力できるようになります。

2. "\r"

こちらはエスケープシーケンスというもので特殊なコマンドを意味し、入力カーソルを行の先頭に移すことができます。


つまり、end=""をprint関数に入れることで、入力後の改行をなくし、"\r"を入れることで、カーソルを前に持っていき、前の表示文字を強制的に上書きしてしまう、というやり方です。

これを応用すれば、次のようにかけます。
もし使用したいのであれば、stepの数を変更するだけで、大体なんとかなる。はず。

sample2.py
import time
progress_bar = "\r[{0}{1}] {2}%"
step = 100
for i in range(step):
    time.sleep(1)
    bar = "#" * (i//10+1)
    nobar = " " * ((step-i)//10)
    print(progress_bar.format(bar, nobar,  round((i+1) / step * 100., 2)), end="")

ここまでが基本のお話。

次に、エスケープシーケンスについて、もう少し詳しく触れます。

エスケープシーケンスで入力箇所を動かす。

エスケープシーケンスとは、ターミナルの制御を可能とする文字で、\033から始まる文字列で表します。
これを利用すれば色の変更などもできるようだが、今回はカーソルを動かすことに焦点を当てます。
使用するシーケンスは以下の通りです。これさえ使いこなせれば、いかようにもターミナル内の文字を操作できるでしょう。

\033[nA ...カーソルをn行だけ上に移動
\033[nB ...カーソルをn行だけ下に移動
\033[nC ...カーソルをn行だけ右に移動
\033[nD ...カーソルをn行だけ左に移動

ただし、Windowsではエスケープシーケンスはデフォルトでは使用できないので、設定を変える必要があります。下に参考になりそうなリンクを掲載しておきました。
また、Jupyterなどの環境はターミナルとは異なるので、上にあげたようなエスケープシーケンスは使用できないようです。

ということで、これらを用いて、動くAAを作ってみましょう。

完成品はこちら

ダンスを披露するAAを進捗バーの下に表示

コードは以下の通り

dancing_progress_bar.py
import time

progress_bar = "\r[{0}{1}] {2}%"
size = 1000
#顔文字がずれているのは自分で修正してください。
dance1 = """\
   ♪ ∧,_∧  ♪              
   ( ´・ω・) ))                
 (( ( つ ヽ、   ♪              
   〉 とノ )))                
  (__ノ^(_)                
"""
dance2 = """\
    ∧_,∧ ♪             
  (( (・ω・` )                  
♪   / ⊂  ) )) ♪               
   (((ヽつ〈                 
   (_)^ヽ__)            
"""

dance3 = """\
     ∧,_∧ ♪            
  (( (    )             
♪    /    ) )) ♪       
  (( (  (  〈             
    (_)^ヽ__)           
"""

dance4 = """\
♪    ∧,_∧                   
   (    ) ))                  
 (( (    ヽ、   ♪           
   〉   ノ ) ))         
  (__ノ^(_)                
"""

dance5 = """\
    ∧_∧ ♪           
   (´・ω・`)  ♪         
   ( つ つ             
 (( (⌒ __) ))          
    し' っ                 
"""

dance6 = """\
  ♪ ∧_∧                   
    ∩´・ω・`)               
    ヽ  ⊂ノ           
   (( (  ⌒)  ))     
      c し'          
"""

dance7 = """\
♪                      
     ∧_∧ ♪           
. ((o(・ω・` )(o))     
   /    /         
   し―-J            
"""

dance8 = """\
♪                     
    ∧_∧              
 ((o(´・ω・)o))       
    ヽ   ヽ ♪           
     し―-J             
"""
dance_list = [dance1,dance2,dance3,dance4,dance5,dance6,dance7,dance8]

for i in range(size):
    time.sleep(0.05)
    progress = int ((i+1)/100)
    maxprog = int(size/100)
    bar = "=" * progress
    nobar = " " * (maxprog - progress)
    dancenum = (i//25)%8
    if i%25==0:
        print(progress_bar.format(bar, nobar,  round((i+1) / size * 100., 2)))
        print(dance_list[dancenum])
        print("\r\33[7A", end = "")
    else:
        print(progress_bar.format(bar, nobar,  round((i+1) / size * 100., 2)), end="")
    if i == size-1:
        for j in range(6):
            print(" "*40)
        print("\r\33[5A",end="")

見て察したとは思いますが、上の8個のAAをひたすら繰り返す進捗バーです。
証拠隠滅のため、進捗100%になった時にAAは消えるように調整しています。
証拠隠滅なんてする必要がない!と思った方は、下の4行を削除すればそのままになるはずです。

せっかくなので、もう一つ用意しておきました。

待機中にお茶を勧めてくるAA

progress_bar_ocha.py
ocha = []

ocha.append("""\

                       _______
       ∧__∧     /__ o、 |、
      (´・ω・)      | ・ \ノ
     旦  o)        | ・  |
     ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
""")
ocha.append("""\

      ジャー       ______
        ∧__∧     /__ o、 |、
      (´・ω・) ノ  .ii | ・ \ノ
     ( o        旦 | ・ |
     ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
""")
ocha.append("""\
     あ、きみもお茶のむ?
              ______
        ∧__∧    /__ o、 |、
     (´・ω・ )    | ・ \ノ
     ( o旦o      | ・ |
     ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
""")

for i in range(3):
    bar = "#" * (i+1)
    nobar = " " * (2-i)
    print("\r[{0}{1}] {2}/3".format(bar, nobar, i+1))
    print(ocha[i])
    time.sleep(5)
    if i != 2:
            print("\r\33[8A",end="")
    if i == 2:
        time.sleep(2)
        print("\r\33[7A",end="")
        for i in range(6):
                print(" "*40)
        print("\r\33[6A",end="")

まとめ

今回、AA進捗バーを作ってみて、エスケープシーケンスの良い勉強になりました。これを応用すれば文字の色なども変えられることも分かったし、以外と学びは多かったです。
これはPython以外でも活用できるようなので、今後仕事でCなどを使う際にも活用していきたいです。

参考サイト

Qiita:Pythonで進捗表示したい!
2chのかわいいAA/顔文字まとめ
Windowsでエスケープシーケンスを有効化する方法