[PYTHON] Verwenden Sie den Fortschrittsbalken mit Klick: (Einfach zu bedienen, verbessern Sie die Anzeige von Aufgaben, die länger als 24 Stunden dauern, Notizen bei Verwendung in der Parallelverarbeitung)

Klick niedlich Klick

Sobald Sie die Bequemlichkeit der Click Library zum Erstellen von Konsolenanwendungen in Python kennen, ist sie argparse oder optparse, sodass Sie nicht einmal wissen, was es bedeutet, Argumente zu analysieren. richtig.

Für Click, Official Site oder einen Einführungsartikel von ikuyamada "Erstellen Sie einfach eine Python-Konsolenanwendung mit Click" Wie du siehst.

Schreiben Sie über den Fortschrittsbalken von Click.

Durch die Anzeige des Arbeitsfortschritts, der mit einem Balken etwas Zeit in Anspruch nimmt, wird der Wartezeitstress für den Benutzer verringert.

Wenn Sie iterable mit click.progressbar () umbrechen, werden Ihnen die Elemente einzeln übergeben und gleichzeitig wird der Fortschrittsbalken ordnungsgemäß aktualisiert.

Stichprobe

Als einfaches Beispiel habe ich eine App geschrieben, die nur mit dem Befehl OS X say herunterzählt. Es unterstützt vier Sprachen: Englisch, Deutsch, Französisch und Japanisch. (Stimme jeder Sprache muss installiert sein) // Wenn Sie kein Macker sind, bearbeiten Sie bitte die Funktion say entsprechend.

countdown.py


#!/usr/bin/env python
#
# countdown
#
import click
import time
import os

_lang = 'en'

def say(num):
    if _lang == 'ja':
        voice = 'Kyoko'
    elif _lang == 'fr':
        voice = 'Virginie'
    elif _lang == 'de':
        voice = 'Anna'
    else:
        voice = 'Agnes'

    os.system('say --voice %s %d' % (voice, num))


@click.command()
@click.argument('count', type=int)
@click.option('--lang', default='en')
def countdown(count, lang):
    global _lang
    _lang = lang

    numbers = range(count, -1, -1)
    with click.progressbar(numbers) as bar:
        for num in bar:
            say(num)
            time.sleep(1)


if __name__ == '__main__':
    countdown()

Ich werde es bewegen.

$ python countdown.py 10 --lang=ja

Wenn der Countdown von 10 beginnt, wird der Fortschrittsbalken wie folgt angezeigt.

[##################------------------]   50%  00:00:06

Die verbleibende Zeit wird vom Fortschritt bis zu diesem Punkt geschätzt und am rechten Ende angezeigt.

click.progressbar () Optionen

click.progressbar(iterable=None, length=None, label=None, show_eta=True, show_percent=None, show_pos=False, item_show_func=None, fill_char='#', empty_char='-', bar_template='%(label)s [%(bar)s] %(info)s', info_sep=' ', width=36, file=None, color=None)

Es gibt eine Erklärung im Originaldokument http://click.pocoo.org/5/api/#click.progressbar, aber vorerst.

--iterable: Iterierbar, um iterierbar zu sein. Wenn Keine, ist eine Längenangabe erforderlich. --length: Die Anzahl der zu iterierenden Elemente. Wenn iterable übergeben wird, wird standardmäßig nach der Länge gefragt (obwohl die Länge möglicherweise nicht bekannt ist). Wenn der Längenparameter angegeben wird, iteriert er für die angegebene Länge (nicht für die iterierbare Länge). --label: Die Beschriftung, die neben dem Fortschrittsbalken angezeigt werden soll. --show_eta: Gibt an, ob die geschätzte erforderliche Zeit angezeigt werden soll. Wenn die Länge nicht bestimmt werden kann, wird sie automatisch ausgeblendet. --show_percent: Gibt an, ob der Prozentsatz angezeigt werden soll. (Standard ist True, wenn iterable Länge hat, andernfalls False) --show_pos: Absolute Position anzeigen (Standard ist False) --item_show_func: Funktion, die beim Anzeigen des zu verarbeitenden Elements aufgerufen wird. Diese Funktion gibt die Zeichenfolge zurück, die im Fortschrittsbalken für das zu verarbeitende Element angezeigt werden soll. Beachten Sie, dass das Element möglicherweise Keine ist. --fill_char: Das Zeichen, das für den Füllteil des Fortschrittsbalkens verwendet wird. --empty_char: Das Zeichen, das für den nicht ausgefüllten Teil des Fortschrittsbalkens verwendet wird. --bar_template: Formatzeichenfolge, die als Balkenvorlage verwendet wird. Die drei darin verwendeten Parameter sind label, bar und info. --info_sep: Trennzeichen für mehrere Informationselemente (eta usw.). --width: Die Breite des Fortschrittsbalkens (in Einheiten von Zeichen halber Breite). Wenn 0 angegeben ist, die volle Breite des Terminals --file - Zieldatei schreiben. Mit Ausnahme des Terminals wird nur die Beschriftung angezeigt. --color: Unterstützt das Terminal ANSI-Farben? (Automatische Identifizierung)

~~ Fallstricke: Ich weiß nicht, wie viele Tage es dauern wird, wenn die verbleibende Zeit 24 Stunden oder mehr beträgt ~~

  • 08.11.2015 Dies ist ein Problem mit der aktuellen Version (5.1) zum Zeitpunkt des Schreibens. Ich habe eine Pull-Anfrage an die Hauptfamilie gesendet, sodass diese möglicherweise zum Zeitpunkt des Lesens des Artikels behoben wurde. → [Pull-Anforderung wurde zusammengeführt] in der Nacht vom 10. November 2015 (https://github.com/mitsuhiko/click/pull/453#event-460027353). Der aktuelle Master hat dieses Problem behoben. Ich denke jedoch, dass einige Leute (Version <= 5.1) die Master-Version vorerst nicht verwenden können, daher werde ich die folgenden Gegenmaßnahmen belassen.

Es ist eine sehr bequeme Funktion, aber es gibt eine Falle.

Lassen Sie uns als Test einen Countdown von 1 Million mit der vorherigen Stichprobe durchführen.

$ python countdown.py 1000000
[------------------------------------]    0%  00:20:47

Noch 20 Minuten bis 999999? Jedes Mal, wenn ich einen zähle, schlafe ich 1 Sekunde, daher sollte es frühestens 11 Tage und 13 Stunden 46 Minuten dauern.

Was meinst du?

Wenn die verbleibende Zeit 24 Stunden oder mehr beträgt, kehrt die Anzeige ab 00:00:00 zurück. Mit anderen Worten, nur der Rest der verbleibenden Zeit geteilt durch 24 Stunden wird angezeigt.

Werfen wir einen Blick auf die Klickquelle.

In der ProgressBar-Klasse (definiert in click / _termui_impl.py) Die benötigte Zeit (eta) wird von einer Mitgliedsfunktion namens format_eta () formatiert.

    @property
    def eta(self):
        if self.length_known and not self.finished:
            return self.time_per_iteration * (self.length - self.pos)
        return 0.0

    def format_eta(self):
        if self.eta_known:
            return time.strftime('%H:%M:%S', time.gmtime(self.eta + 1))
        return ''

Es ist so implementiert. Es ist traurig, dass nur Aufgaben erwartet werden, die innerhalb von 24 Stunden erledigt werden können.

Sollte ich hier "% H:% M:% S" in "% d% H:% M:% S" ändern? Es ist nicht.

$ python
Python 2.7.9 (default, Jan  7 2015, 11:50:42) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(0))
'1970-01-01 00:00:00'
>>> time.strftime('%H:%M:%S', time.gmtime(0))
'00:00:00'
>>> time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(60*60*24))
'1970-01-02 00:00:00'
>>> time.strftime('%H:%M:%S', time.gmtime(60*60*24))
'00:00:00'
```

 `` time.gmtime (t) `` ist eine Struktur, die aus 9 Elementen besteht: Jahr, Monat, Tag, Stunde, Minute, Sekunde, Tag, welcher Tag des Jahres und Sommerzeit. Ich gebe `time.struct_time`` zurück
 Da es auf der Unix-Epoche (1. Januar 1970, 0:00:00 Uhr) basiert, ist `` time.gmtime (0) `` "1. Januar 1970" 0:00:00 Werfen Sie dies in Sekunden in `` time.stftime () `` und nehmen Sie ``% Y-% m-% d``, um "1970-01-01" zu erhalten.
 (Der Grund, warum die Implementierung von `` format_eta () `` mit ``% H:% M:% S'`` funktioniert, ist, dass die Unix-Epoche um 00:00:00 Uhr nicht auf halbem Weg ist. )

 Wenn Sie also die tägliche Einheit berücksichtigen, ist es besser, selbst zu berechnen, ohne sich auf "time.gmtime" oder "time.strftime" zu verlassen.

 Zum Beispiel:

```
def format_eta_impl(self):
    if self.eta_known:
        t = self.eta
        seconds = t % 60
        t /= 60
        minutes = t % 60
        t /= 60
        hours = t % 24
        t /= 24
        if t > 0:
            days = t
            return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
        else:
            return '%02d:%02d:%02d' % (hours, minutes, seconds)
    else:
        return ''
```

 Ich möchte die Mitgliedsfunktion `` format_eta (self) `` der ProgressBar-Klasse durch diese ersetzen, aber da die ProgressBar-Klasse selbst in click / _termui_impl.py definiert ist, ist sie für Bibliotheksbenutzer unsichtbar.

```
    with click.progressbar(numbers) as bar:
        bar.__class__.format_eta = format_eta_impl
        for num in bar:
            say(num)
            time.sleep(1)
```

 Es sieht so aus, als ob es so gemacht werden sollte.
 Dies funktioniert nicht mit `` bar.format_eta = format_eta_impl``.
 (Ich werde die Erklärung dieses Bereichs hier weglassen!)

 Lass es uns benutzen.

```
$ python countdown.py 1000000
[------------------------------------]    0%  18d 23:20:12
```

 Der Countdown von One Million hat begonnen.
 Noch 18 Tage und 23 Stunden 20 Minuten 12 Sekunden ww


 damit.
 (´-`) .. oO (Du solltest es richtig schreiben und an die Kopffamilie senden. Klick)
 → [Gesendet](https://github.com/mitsuhiko/click/pull/453)
 → [Zusammengeführt](https://github.com/mitsuhiko/click/pull/453#event-460027353)

## countdown.py (verbesserte Version)


#### **`big_countdown.py`**
```py

#!/usr/bin/env python
#
# countdown (for big number)
#
import click
import time
import os

_lang = 'en'

def say(num):
    if _lang == 'ja':
        voice = 'Kyoko'
    elif _lang == 'fr':
        voice = 'Virginie'
    elif _lang == 'de':
        voice = 'Anna'
    else:
        voice = 'Agnes'

    os.system('say --voice %s %d' % (voice, num))


def format_eta_impl(self):
    if self.eta_known:
        t = self.eta
        seconds = t % 60
        t /= 60
        minutes = t % 60
        t /= 60
        hours = t % 24
        t /= 24
        if t > 0:
            days = t
            return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
        else:
            return '%02d:%02d:%02d' % (hours, minutes, seconds)
    else:
        return ''


@click.command()
@click.argument('count', type=int)
@click.option('--lang', default='en')
def countdown(count, lang):
    global _lang
    _lang = lang

    numbers = range(count, -1, -1)
    with click.progressbar(numbers) as bar:
        bar.__class__.format_eta = format_eta_impl
        for num in bar:
            say(num)
            time.sleep(1)

if __name__ == '__main__':
    countdown()
```

## Vorsichtsmaßnahmen bei Verwendung von click.progressbar für Aufgaben, die parallel verarbeitet werden sollen

### Symptome

 ――Der Fortschrittsbalken bewegt sich zunächst um etwa die Hälfte, obwohl nichts voranschreitet
 ――Die Aufgabe ist nicht für immer abgeschlossen, obwohl die Anzeige der verbleibenden Zeit etwa 1 bis 2 Sekunden beträgt.

 Für diejenigen, die Probleme mit diesen Symptomen haben.

 Wenn Sie beispielsweise multiprocessing.pool.Pool verwenden, wird die Fortschrittsanzeige des Fortschrittsbalkens um "Wenn der Prozess in den Pool gestellt wird" erhöht (und nicht um den Zeitpunkt, zu dem der Prozess tatsächlich stattgefunden hat).

 Aus diesem Grund tritt ein Phänomen auf, bei dem der Fortschrittsbalken zuerst um etwa die Hälfte vorrückt, obwohl keine Verarbeitung fortgeschritten ist.

 Der Fortschrittsbalken scheint im ersten Moment etwa die Hälfte der Verarbeitung abgeschlossen zu haben, sodass die verbleibende Verarbeitungszeit auf 1 oder 2 Sekunden geschätzt und angezeigt wird. Dies ist die Ursache für das Phänomen, dass die Aufgabe nicht für immer abgeschlossen ist, obwohl die verbleibende Zeitanzeige etwa 1 bis 2 Sekunden beträgt.

### Gegenmaßnahmen

 Um Fortschritte zu erzielen, zeigen Sie den Fortschritt zum Zeitpunkt der tatsächlichen Verarbeitung an, damit die verbleibende Zeit so genau wie möglich geschätzt werden kann

 --Set length = (Größe von iterable) im Argument von click.progressbar () (nicht iterable selbst)
 - Rufen Sie jedes Mal bar.update (1) auf, wenn Sie eines verarbeiten

### Strg-C-Kill funktioniert nicht mit Multiprocessing

 Es ist nicht die Schuld von Click, sondern ... eine benutzerfreundliche CLI zu erstellen.
 (Es gibt multiprocessing.pool.Pool)
 Der Pool hat den KeyboardInterrupt aufgenommen. Stellen Sie den Initialisierer so ein, dass der KeyboardInterrupt auf der Poolseite ignoriert wird.

### Implementierungsbeispiel

 Ich habe versucht, das Countdown-Lesebeispiel zu parallelisieren.
 (Es fühlt sich schlecht an)


#### **`countdown_mp.py`**
```py

import click
import time
import os
import multiprocessing
from multiprocessing.pool import Pool
from functools import partial
import signal

_lang = 'en'

def say(num):
    if _lang == 'ja':
        voice = 'Kyoko'
    elif _lang == 'fr':
        voice = 'Virginie'
    elif _lang == 'de':
        voice = 'Anna'
    else:
        voice = 'Agnes'

    os.system('say --voice %s %d' % (voice, num))
    time.sleep(1)

    return num


def _init_worker():
    signal.signal(signal.SIGINT, signal.SIG_IGN)


@click.command()
@click.argument('count', type=int)
@click.option('--lang', default='en')
@click.argument('pool-size', type=int, default=multiprocessing.cpu_count())
@click.argument('chunk-size', type=int, default=5)
@click.argument('max-tasks-per-child', type=int, default=3)
def countdown(count, lang, pool_size, chunk_size, max_tasks_per_child):
    global _lang
    _lang = lang

    pool = Pool(pool_size, _init_worker, maxtasksperchild=max_tasks_per_child)
    imap_func = partial(pool.imap, chunksize=chunk_size)

    numbers = range(count, -1, -1)

    with click.progressbar(length=len(numbers)) as bar:
        for result in imap_func(say, numbers):
            bar.update(1)

    pool.close()


if __name__ == '__main__':
    countdown()
```


Recommended Posts

Verwenden Sie den Fortschrittsbalken mit Klick: (Einfach zu bedienen, verbessern Sie die Anzeige von Aufgaben, die länger als 24 Stunden dauern, Notizen bei Verwendung in der Parallelverarbeitung)
Echtzeitanzeige des serverseitigen Verarbeitungsfortschritts im Browser (Implementierung des Fortschrittsbalkens)
Erhalten Sie eine Liste der Ergebnisse der Parallelverarbeitung in Python mit Starmap