web-dev-qa-db-ja.com

pythonスクリプトの印刷コマンドをtqdm.write()を介してリダイレクトします

Python=でtqdmを使用して、スクリプトにコンソールプログレスバーを表示しています。ただし、コンソールにprintメッセージを送信する関数を呼び出す必要があります。一般的に、コンソールにプログレスバーを表示しながらコンソールに書き込むと、次のように表示が乱れます。

_from time import sleep
from tqdm import tqdm

def blabla():
  print "Foo blabla"

for k in tqdm(range(3)):
  blabla()
  sleep(.5)
_

これにより出力が作成されます。

_0%|                                           | 0/3 [00:00<?, ?it/s]Foo
blabla
33%|###########6                       | 1/3 [00:00<00:01,  2.00it/s]Foo
blabla
67%|#######################3           | 2/3 [00:01<00:00,  2.00it/s]Foo
blabla
100%|###################################| 3/3 [00:01<00:00,  2.00it/s]
_

tqdm のドキュメントによると、メソッドtqdm.write()は、表示されたプログレスバーを壊さずにコンソールにメッセージを書き込む手段を提供します。したがって、正しい出力は次のスニペットによって提供されます。

_from time import sleep
from tqdm import tqdm

def blabla():
  tqdm.write("Foo blabla")

for k in tqdm(range(3)):
  blabla()
  sleep(.5)
_

そして、このようになります:

_Foo blabla
Foo blabla
Foo blabla
100%|###################################| 3/3 [00:01<00:00,  1.99it/s]
_

一方、この ソリューションは、_sys.stdout_をvoidに非常にエレガントにリダイレクトすることで、これらの関数 を沈黙させることができます。これは、機能を無音にするのに最適です。

それでもプログレスバーを壊さずにこれらの関数からのメッセージを表示したいので、_sys.stdout_をtqdm.write()にリダイレクトし、次にtqdm.write()old_sys.stdout_に書き込みます。これはスニペットになります:

_from time import sleep

import contextlib
import sys

from tqdm import tqdm

class DummyFile(object):
  file = None
  def __init__(self, file):
    self.file = file

  def write(self, x):
    tqdm.write(x, file=self.file)

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = DummyFile(save_stdout)
    yield
    sys.stdout = save_stdout

def blabla():
  print "Foo blabla"

for k in tqdm(range(3)):
  with nostdout():
    blabla()
    sleep(.5)
_

ただし、これにより、以前のように実際にはさらにめちゃくちゃな出力が作成されます。

_0%|                                           | 0/3 [00:00<?, ?it/s]Foo
blabla


33%|###########6                       | 1/3 [00:00<00:01,  2.00it/s]Foo
blabla


67%|#######################3           | 2/3 [00:01<00:00,  2.00it/s]Foo
blabla


100%|###################################| 3/3 [00:01<00:00,  2.00it/s]
_

参考:tqdm.write(..., end="")の内部でDummyFile.write()を呼び出すと、まだめちゃくちゃになっている最初の出力と同じ結果が作成されます。

tqdm.write()は、メッセージを書き込む前にプログレスバーをクリアしてから、プログレスバーを書き直すため、これが機能しない理由を理解できません。

何が欠けていますか?

13

_sys.stdout_のリダイレクトは常にトリッキーであり、2つのアプリケーションが同時にそれをいじるときに悪夢になります。

ここでのトリックは、tqdmがデフォルトで_sys.stderr_ではなく_sys.stdout_に出力することです。通常、tqdmには、これら2つの特別なチャネルに対するアンチミックスアップ戦略がありますが、_sys.stdout_をリダイレクトしているため、ファイルハンドルが変更されるため、tqdmは混乱します。

したがって、_file=sys.stdout_をtqdmに明示的に指定するだけで機能します。

_from time import sleep

import contextlib
import sys

from tqdm import tqdm

class DummyFile(object):
  file = None
  def __init__(self, file):
    self.file = file

  def write(self, x):
    # Avoid print() second call (useless \n)
    if len(x.rstrip()) > 0:
        tqdm.write(x, file=self.file)

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = DummyFile(sys.stdout)
    yield
    sys.stdout = save_stdout

def blabla():
  print("Foo blabla")

# tqdm call to sys.stdout must be done BEFORE stdout redirection
# and you need to specify sys.stdout, not sys.stderr (default)
for _ in tqdm(range(3), file=sys.stdout):
    with nostdout():
        blabla()
        sleep(.5)

print('Done!')
_

また、出力をより良くするためにいくつかのトリックを追加しました(たとえば、_\n_なしでprint()を使用する場合、役に立たない_end=''_はありません)。

/ EDIT:実際には、stdoutを開始した後でtqdmリダイレクトを実行できるようです。tqdmで_dynamic_ncols=True_を指定するだけです。

15
gaborous

悪いかもしれませんが、内蔵の印刷機能を変更しました。

import inspect
import tqdm
# store builtin print
old_print = print
def new_print(*args, **kwargs):
    # if tqdm.tqdm.write raises error, use builtin print
    try:
        tqdm.tqdm.write(*args, **kwargs)
    except:
        old_print(*args, ** kwargs)
# globaly replace print with new_print
inspect.builtins.print = new_print
6
user493630

User493630と複雑な答えを組み合わせて、tqdmfile=sys.stdoutパラメータを使用する必要がないようにするこのコンテキストマネージャを作成しました。

import inspect
import contextlib
import tqdm

@contextlib.contextmanager
def redirect_to_tqdm():
    # Store builtin print
    old_print = print
    def new_print(*args, **kwargs):
        # If tqdm.tqdm.write raises error, use builtin print
        try:
            tqdm.tqdm.write(*args, **kwargs)
        except:
            old_print(*args, ** kwargs)

    try:
        # Globaly replace print with new_print
        inspect.builtins.print = new_print
        yield
    finally:
        inspect.builtins.print = old_print

それを使用するには、単に:

for i in tqdm.tqdm(range(100)):
    with redirect_to_tqdm():
        time.sleep(.1)
        print(i)

さらに簡単にするために、コードを新しい関数でラップすることができます:

def tqdm_redirect(*args, **kwargs):
    with redirect_to_tqdm():
        for x in tqdm.tqdm(*args, **kwargs):
            yield x

for i in tqdm_redirect(range(20)):
    time.sleep(.1)
    print(i)
2
Conchylicultor

OPのソリューションはほぼ正しいです。あなたの出力を台無しにするtqdmライブラリのテストはこれです( https://github.com/tqdm/tqdm/blob/master/tqdm/_tqdm.py#L546-L549 ):

_if hasattr(inst, "start_t") and (inst.fp == fp or all(
           f in (sys.stdout, sys.stderr) for f in (fp, inst. 
    inst.clear(nolock=True)
    inst_cleared.append(inst)
_

_tqdm.write_は、提供するファイルをテストして、印刷するテキストと潜在的なtqdmバーの間に衝突のリスクがないかどうかを確認しています。あなたの場合、stdoutとstderrがターミナルで混合されるので、衝突があります。これに対抗するため、テストに合格すると、tqdmはバーをクリアし、テキストを印刷して、後でバーを描画します。

ここで、テスト_fp == sys.stdout_は、_sys.stdout_がDummyFileになり、fpが実際の_sys.stdout_であるために失敗し、クリーニング動作は有効になっていません。 DummyFileの単純な等価演算子はすべてを修正します。

_class DummyFile(object):
    def __init__(self, file):
        self.file = file

    def write(self, x):
        tqdm.write(x, end="", file=self.file)

    def __eq__(self, other):
        return other is self.file
_

また、printは改行を_sys.stdout_に渡すため(またはユーザーの選択に依存しないため)、tqdmが独自に別の行を追加することを望まないため、オプション_end=''_よりもコンテンツに対してstripを実行します。

このソリューションの利点

Gaborousの答えにより、tqdm(..., file=sys.stdout)は出力ストリームを小節で汚染します。 _file=sys.stdout_(デフォルト)を維持することにより、ストリームを分離したままにできます。
Conchylicultorとuser493630の回答を使用すると、パッチ印刷のみを行うことができます。ただし、ロギングなどの他のシステムはsys.stdoutに直接ストリーミングするため、_tqdm.write_を経由しません。

0
Perceval W