Argparse-Tutorial
*****************

Dieses Tutorial soll eine sanfte Einführung in "argparse" sein, das
empfohlene Modul zum Zerlegen der Befehlszeile in der Python-
Standardbibliothek.

Bemerkung:

  Die Standardbibliothek enthält zwei weitere Bibliotheken, die
  unmittelbar mit der Verarbeitung von Befehlszeilenparametern zu tun
  haben: das tieferliegende Modul "optparse" (das für eine gegebene
  Anwendung möglicherweise mehr Code zur Einrichtung braucht, einer
  Anwendung aber auch Verhalten erlaubt, das "argparse" nicht
  unterstützt) und das sehr tiefliegende "getopt" (das ausdrücklich
  als Gegenstück zur Funktionsfamilie "getopt()" dient, die
  C-Programmierern zur Verfügung steht). Keines dieser beiden Module
  wird in dieser Anleitung unmittelbar behandelt, aber viele der
  Kernkonzepte von "argparse" stammen ursprünglich aus "optparse".
  Manche Teile dieses Tutorials sind daher auch für Nutzer von
  "optparse" von Belang.


Begriffe
========

Zeigen wir anhand des Befehls **ls**, welche Art von Funktionalität
wir in diesem einführenden Tutorial erkunden werden:

   $ ls
   cpython  devguide  prog.py  pypy  rm-unused-function.patch
   $ ls pypy
   ctypes_configure  demo  dotviewer  include  lib_pypy  lib-python ...
   $ ls -l
   total 20
   drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
   drwxr-xr-x  4 wena wena 4096 Feb  8 12:04 devguide
   -rwxr-xr-x  1 wena wena  535 Feb 19 00:05 prog.py
   drwxr-xr-x 14 wena wena 4096 Feb  7 00:59 pypy
   -rw-r--r--  1 wena wena  741 Feb 18 01:01 rm-unused-function.patch
   $ ls --help
   Usage: ls [OPTION]... [FILE]...
   List information about the FILEs (the current directory by default).
   Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
   ...

Ein paar Begriffe, die wir aus den vier Befehlen lernen können:

* Der Befehl **ls** ist nützlich, auch wenn er ganz ohne Optionen
  ausgeführt wird. Standardmäßig zeigt er den Inhalt des aktuellen
  Verzeichnisses an.

* Wollen wir mehr als das, was er standardmäßig bietet, sagen wir ihm
  ein bisschen mehr. In diesem Fall wollen wir, dass er ein anderes
  Verzeichnis anzeigt, "pypy". Wir haben damit das angegeben, was als
  positionelles Argument bekannt ist. Es heißt so, weil das Programm
  allein daran, wo der Wert auf der Befehlszeile steht, erkennen soll,
  was es damit tun muss. Deutlicher wird dieser Begriff bei einem
  Befehl wie **cp**, dessen grundlegendste Verwendung "cp SRC DEST"
  ist. Die erste Position ist *das, was kopiert werden soll*, und die
  zweite Position ist *das Ziel, wohin es kopiert werden soll*.

* Nehmen wir nun an, wir wollen das Verhalten des Programms ändern. In
  unserem Beispiel zeigen wir für jede Datei mehr Informationen an,
  anstatt nur die Dateinamen. Das "-l" in diesem Fall ist als
  optionales Argument bekannt.

* Das ist ein Ausschnitt des Hilfetexts. Er ist sehr nützlich, weil du
  auf ein Programm stoßen kannst, das du noch nie verwendet hast, und
  allein durch das Lesen seines Hilfetexts herausfinden kannst, wie es
  funktioniert.


Die Grundlagen
==============

Beginnen wir mit einem ganz einfachen Beispiel, das (fast) nichts tut:

   import argparse
   parser = argparse.ArgumentParser()
   parser.parse_args()

Folgendes ist das Ergebnis, wenn man den Code ausführt:

   $ python prog.py
   $ python prog.py --help
   usage: prog.py [-h]

   options:
     -h, --help  show this help message and exit
   $ python prog.py --verbose
   usage: prog.py [-h]
   prog.py: error: unrecognized arguments: --verbose
   $ python prog.py foo
   usage: prog.py [-h]
   prog.py: error: unrecognized arguments: foo

Das passiert hier:

* Das Skript ohne Optionen auszuführen zeigt nichts auf der
  Standardausgabe. Nicht so nützlich.

* Das zweite beginnt, die Nützlichkeit des Moduls "argparse" zu
  zeigen. Wir haben so gut wie nichts getan, bekommen aber schon eine
  schöne Hilfemeldung.

* Die Option "--help", die sich auch zu "-h" verkürzen lässt, ist die
  einzige Option, die wir umsonst bekommen (also ohne sie angeben zu
  müssen). Alles andere anzugeben führt zu einem Fehler. Aber selbst
  dann bekommen wir eine nützliche Verwendungsmeldung – ebenfalls
  umsonst.


Positionelle Argumente einführen
================================

Ein Beispiel:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("echo")
   args = parser.parse_args()
   print(args.echo)

Und der Code ausgeführt:

   $ python prog.py
   usage: prog.py [-h] echo
   prog.py: error: the following arguments are required: echo
   $ python prog.py --help
   usage: prog.py [-h] echo

   positional arguments:
     echo

   options:
     -h, --help  show this help message and exit
   $ python prog.py foo
   foo

Das passiert hier:

* Wir haben die Methode "add_argument()" ergänzt, mit der wir angeben,
  welche Befehlszeilenoptionen das Programm annehmen soll. In diesem
  Fall habe ich sie "echo" genannt, damit es zu ihrer Aufgabe passt.

* Unser Programm aufzurufen erfordert jetzt, dass wir eine Option
  angeben.

* Die Methode "parse_args()" gibt tatsächlich einige Daten aus den
  angegebenen Optionen zurück, in diesem Fall "echo".

* Die Variable ist eine Art „Magie“, die "argparse" umsonst erledigt
  (du musst also nicht angeben, in welcher Variablen dieser Wert
  gespeichert wird). Dir wird auch auffallen, dass ihr Name mit dem
  Zeichenkettenargument übereinstimmt, das der Methode übergeben
  wurde: "echo".

Beachte aber, dass die Hilfeausgabe zwar schön aussieht, derzeit aber
nicht so hilfreich ist, wie sie sein könnte. Wir sehen zum Beispiel,
dass wir "echo" als positionelles Argument bekommen haben, wissen aber
nicht, was es tut – außer durch Raten oder durch Lesen des Quellcodes.
Machen wir es also etwas nützlicher:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("echo", help="echo the string you use here")
   args = parser.parse_args()
   print(args.echo)

Und wir bekommen:

   $ python prog.py -h
   usage: prog.py [-h] echo

   positional arguments:
     echo        echo the string you use here

   options:
     -h, --help  show this help message and exit

Wie wäre es nun mit etwas noch Nützlicherem:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", help="display a square of a given number")
   args = parser.parse_args()
   print(args.square**2)

Folgendes ist das Ergebnis, wenn man den Code ausführt:

   $ python prog.py 4
   Traceback (most recent call last):
     File "prog.py", line 5, in <module>
       print(args.square**2)
   TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

Das lief nicht so gut. Der Grund: "argparse" behandelt die Optionen,
die wir ihm geben, als Zeichenketten, sofern wir nichts anderes sagen.
Sagen wir "argparse" also, es soll diese Eingabe als Ganzzahl
behandeln:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", help="display a square of a given number",
                       type=int)
   args = parser.parse_args()
   print(args.square**2)

Folgendes ist das Ergebnis, wenn man den Code ausführt:

   $ python prog.py 4
   16
   $ python prog.py four
   usage: prog.py [-h] square
   prog.py: error: argument square: invalid int value: 'four'

Das lief gut. Das Programm bricht jetzt sogar hilfreich bei
unzulässiger Eingabe ab, bevor es weitermacht.


Optionale Argumente einführen
=============================

Bisher haben wir mit positionellen Argumenten gespielt. Sehen wir uns
an, wie man optionale hinzufügt:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("--verbosity", help="increase output verbosity")
   args = parser.parse_args()
   if args.verbosity:
       print("verbosity turned on")

Und die Ausgabe:

   $ python prog.py --verbosity 1
   verbosity turned on
   $ python prog.py
   $ python prog.py --help
   usage: prog.py [-h] [--verbosity VERBOSITY]

   options:
     -h, --help            show this help message and exit
     --verbosity VERBOSITY
                           increase output verbosity
   $ python prog.py --verbosity
   usage: prog.py [-h] [--verbosity VERBOSITY]
   prog.py: error: argument --verbosity: expected one argument

Das passiert hier:

* Das Programm ist so geschrieben, dass es etwas anzeigt, wenn "--
  verbosity" angegeben ist, und nichts, wenn nicht.

* Um zu zeigen, dass die Option tatsächlich optional ist: Es gibt
  keinen Fehler, wenn man das Programm ohne sie ausführt. Beachte,
  dass die betroffene Variable – in diesem Fall "args.verbosity" –
  standardmäßig den Wert "None" bekommt, wenn ein optionales Argument
  nicht verwendet wird. Deshalb scheitert sie an der Wahrheitsprüfung
  der "if"-Anweisung.

* Die Hilfemeldung sieht etwas anders aus.

* Bei Verwendung der Option "--verbosity" muss man außerdem einen Wert
  angeben, irgendeinen Wert.

Das obige Beispiel nimmt für "--verbosity" beliebige ganzzahlige Werte
an, aber für unser einfaches Programm sind tatsächlich nur zwei Werte
nützlich: "True" oder "False". Ändern wir den Code entsprechend:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("--verbose", help="increase output verbosity",
                       action="store_true")
   args = parser.parse_args()
   if args.verbose:
       print("verbosity turned on")

Und die Ausgabe:

   $ python prog.py --verbose
   verbosity turned on
   $ python prog.py --verbose 1
   usage: prog.py [-h] [--verbose]
   prog.py: error: unrecognized arguments: 1
   $ python prog.py --help
   usage: prog.py [-h] [--verbose]

   options:
     -h, --help  show this help message and exit
     --verbose   increase output verbosity

Das passiert hier:

* Die Option ist jetzt mehr ein Schalter als etwas, das einen Wert
  braucht. Wir haben sogar den Namen der Option geändert, damit er zu
  dieser Idee passt. Beachte, dass wir nun ein neues Schlüsselwort
  angeben, "action", und ihm den Wert ""store_true"" geben. Das
  bedeutet: Ist die Option angegeben, weise "args.verbose" den Wert
  "True" zu. Sie nicht anzugeben bedeutet "False".

* Es beschwert sich, wenn du einen Wert angibst – ganz im Sinne
  dessen, was Schalter eigentlich sind.

* Beachte den anderen Hilfetext.


Kurze Optionen
--------------

Wenn du mit der Verwendung der Befehlszeile vertraut bist, wird dir
auffallen, dass ich das Thema der kurzen Fassungen der Optionen noch
nicht angesprochen habe. Es ist recht einfach:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("-v", "--verbose", help="increase output verbosity",
                       action="store_true")
   args = parser.parse_args()
   if args.verbose:
       print("verbosity turned on")

Und los geht's:

   $ python prog.py -v
   verbosity turned on
   $ python prog.py --help
   usage: prog.py [-h] [-v]

   options:
     -h, --help     show this help message and exit
     -v, --verbose  increase output verbosity

Beachte, dass die neue Fähigkeit sich auch im Hilfetext widerspiegelt.


Positionelle und optionale Argumente verbinden
==============================================

Unser Programm wird immer komplexer:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display a square of a given number")
   parser.add_argument("-v", "--verbose", action="store_true",
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2
   if args.verbose:
       print(f"the square of {args.square} equals {answer}")
   else:
       print(answer)

Und nun die Ausgabe:

   $ python prog.py
   usage: prog.py [-h] [-v] square
   prog.py: error: the following arguments are required: square
   $ python prog.py 4
   16
   $ python prog.py 4 --verbose
   the square of 4 equals 16
   $ python prog.py --verbose 4
   the square of 4 equals 16

* Wir haben ein positionelles Argument zurückgebracht, daher die
  Beschwerde.

* Beachte, dass die Reihenfolge keine Rolle spielt.

Wie wäre es, wenn wir unserem Programm die Fähigkeit zu mehreren
Werten für die Ausführlichkeit zurückgeben und sie tatsächlich nutzen:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display a square of a given number")
   parser.add_argument("-v", "--verbosity", type=int,
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2
   if args.verbosity == 2:
       print(f"the square of {args.square} equals {answer}")
   elif args.verbosity == 1:
       print(f"{args.square}^2 == {answer}")
   else:
       print(answer)

Und die Ausgabe:

   $ python prog.py 4
   16
   $ python prog.py 4 -v
   usage: prog.py [-h] [-v VERBOSITY] square
   prog.py: error: argument -v/--verbosity: expected one argument
   $ python prog.py 4 -v 1
   4^2 == 16
   $ python prog.py 4 -v 2
   the square of 4 equals 16
   $ python prog.py 4 -v 3
   16

Die sehen alle gut aus – außer dem letzten, der einen Fehler in
unserem Programm offenlegt. Beheben wir ihn, indem wir die Werte
einschränken, die die Option "--verbosity" annehmen kann:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display a square of a given number")
   parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2
   if args.verbosity == 2:
       print(f"the square of {args.square} equals {answer}")
   elif args.verbosity == 1:
       print(f"{args.square}^2 == {answer}")
   else:
       print(answer)

Und die Ausgabe:

   $ python prog.py 4 -v 3
   usage: prog.py [-h] [-v {0,1,2}] square
   prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
   $ python prog.py 4 -h
   usage: prog.py [-h] [-v {0,1,2}] square

   positional arguments:
     square                display a square of a given number

   options:
     -h, --help            show this help message and exit
     -v, --verbosity {0,1,2}
                           increase output verbosity

Beachte, dass sich die Änderung sowohl in der Fehlermeldung als auch
in der Hilfezeichenkette widerspiegelt.

Nehmen wir nun einen anderen Ansatz für das Spiel mit der
Ausführlichkeit, der recht verbreitet ist. Er entspricht auch der Art,
wie die CPython-Programmdatei ihr eigenes Argument für die
Ausführlichkeit behandelt (sieh dir die Ausgabe von "python --help"
an):

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display the square of a given number")
   parser.add_argument("-v", "--verbosity", action="count",
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2
   if args.verbosity == 2:
       print(f"the square of {args.square} equals {answer}")
   elif args.verbosity == 1:
       print(f"{args.square}^2 == {answer}")
   else:
       print(answer)

Wir haben eine weitere Aktion eingeführt, „count“, um die Anzahl der
Vorkommen bestimmter Optionen zu zählen.

   $ python prog.py 4
   16
   $ python prog.py 4 -v
   4^2 == 16
   $ python prog.py 4 -vv
   the square of 4 equals 16
   $ python prog.py 4 --verbosity --verbosity
   the square of 4 equals 16
   $ python prog.py 4 -v 1
   usage: prog.py [-h] [-v] square
   prog.py: error: unrecognized arguments: 1
   $ python prog.py 4 -h
   usage: prog.py [-h] [-v] square

   positional arguments:
     square           display a square of a given number

   options:
     -h, --help       show this help message and exit
     -v, --verbosity  increase output verbosity
   $ python prog.py 4 -vvv
   16

* Ja, es ist jetzt – wie in der vorigen Fassung unseres Skripts – mehr
  ein Schalter (ähnlich wie "action="store_true""). Das sollte die
  Beschwerde erklären.

* Es verhält sich auch ähnlich wie die Aktion „store_true“.

* Und hier eine Vorführung dessen, was die Aktion „count“ liefert.
  Diese Art der Verwendung hast du wahrscheinlich schon gesehen.

* Und wenn du den Schalter "-v" nicht angibst, gilt dieser Schalter
  als mit dem Wert "None" belegt.

* Wie zu erwarten, bekommen wir dieselbe Ausgabe, wenn wir die lange
  Form des Schalters angeben.

* Unsere Hilfeausgabe ist leider nicht sehr aufschlussreich über die
  neue Fähigkeit, die unser Skript erworben hat, aber das lässt sich
  immer beheben, indem wir die Dokumentation unseres Skripts
  verbessern (etwa über das Schlüsselwortargument "help").

* Diese letzte Ausgabe legt einen Fehler in unserem Programm offen.

Beheben wir ihn:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display a square of a given number")
   parser.add_argument("-v", "--verbosity", action="count",
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2

   # bugfix: replace == with >=
   if args.verbosity >= 2:
       print(f"the square of {args.square} equals {answer}")
   elif args.verbosity >= 1:
       print(f"{args.square}^2 == {answer}")
   else:
       print(answer)

Und das ergibt Folgendes:

   $ python prog.py 4 -vvv
   the square of 4 equals 16
   $ python prog.py 4 -vvvv
   the square of 4 equals 16
   $ python prog.py 4
   Traceback (most recent call last):
     File "prog.py", line 11, in <module>
       if args.verbosity >= 2:
   TypeError: '>=' not supported between instances of 'NoneType' and 'int'

* Die erste Ausgabe lief gut und behebt den Fehler, den wir vorher
  hatten. Wir wollen nämlich, dass jeder Wert >= 2 so ausführlich wie
  möglich ist.

* Die dritte Ausgabe nicht so gut.

Beheben wir diesen Fehler:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("square", type=int,
                       help="display a square of a given number")
   parser.add_argument("-v", "--verbosity", action="count", default=0,
                       help="increase output verbosity")
   args = parser.parse_args()
   answer = args.square**2
   if args.verbosity >= 2:
       print(f"the square of {args.square} equals {answer}")
   elif args.verbosity >= 1:
       print(f"{args.square}^2 == {answer}")
   else:
       print(answer)

Wir haben gerade noch ein Schlüsselwort eingeführt: "default". Wir
haben es auf "0" gesetzt, damit es mit den anderen int-Werten
vergleichbar ist. Denk daran, dass ein optionales Argument
standardmäßig den Wert "None" bekommt, wenn es nicht angegeben wird,
und dass dieser sich nicht mit einem int-Wert vergleichen lässt (daher
die "TypeError"-Ausnahme).

Und:

   $ python prog.py 4
   16

Mit dem, was wir bisher gelernt haben, kommst du schon recht weit –
und wir haben nur an der Oberfläche gekratzt. Das Modul "argparse" ist
sehr mächtig, und wir erkunden noch ein wenig mehr davon, bevor wir
dieses Tutorial beenden.


Etwas fortgeschrittener
=======================

Was, wenn wir unser kleines Programm erweitern wollten, damit es
andere Potenzen berechnet und nicht nur Quadrate:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("x", type=int, help="the base")
   parser.add_argument("y", type=int, help="the exponent")
   parser.add_argument("-v", "--verbosity", action="count", default=0)
   args = parser.parse_args()
   answer = args.x**args.y
   if args.verbosity >= 2:
       print(f"{args.x} to the power {args.y} equals {answer}")
   elif args.verbosity >= 1:
       print(f"{args.x}^{args.y} == {answer}")
   else:
       print(answer)

Ausgabe:

   $ python prog.py
   usage: prog.py [-h] [-v] x y
   prog.py: error: the following arguments are required: x, y
   $ python prog.py -h
   usage: prog.py [-h] [-v] x y

   positional arguments:
     x                the base
     y                the exponent

   options:
     -h, --help       show this help message and exit
     -v, --verbosity
   $ python prog.py 4 2 -v
   4^2 == 16

Beachte, dass wir die Stufe der Ausführlichkeit bisher verwendet
haben, um den angezeigten Text zu *ändern*. Das folgende Beispiel
verwendet die Stufe der Ausführlichkeit stattdessen, um *mehr* Text
anzuzeigen:

   import argparse
   parser = argparse.ArgumentParser()
   parser.add_argument("x", type=int, help="the base")
   parser.add_argument("y", type=int, help="the exponent")
   parser.add_argument("-v", "--verbosity", action="count", default=0)
   args = parser.parse_args()
   answer = args.x**args.y
   if args.verbosity >= 2:
       print(f"Running '{__file__}'")
   if args.verbosity >= 1:
       print(f"{args.x}^{args.y} == ", end="")
   print(answer)

Ausgabe:

   $ python prog.py 4 2
   16
   $ python prog.py 4 2 -v
   4^2 == 16
   $ python prog.py 4 2 -vv
   Running 'prog.py'
   4^2 == 16


Uneindeutige Argumente angeben
------------------------------

Wenn es uneindeutig ist, ob ein Argument positionell ist oder zu einem
Argument gehört, lässt sich "--" verwenden, um "parse_args()" zu
sagen, dass alles danach ein positionelles Argument ist:

   >>> parser = argparse.ArgumentParser(prog='PROG')
   >>> parser.add_argument('-n', nargs='+')
   >>> parser.add_argument('args', nargs='*')

   >>> # ambiguous, so parse_args assumes it's an option
   >>> parser.parse_args(['-f'])
   usage: PROG [-h] [-n N [N ...]] [args ...]
   PROG: error: unrecognized arguments: -f

   >>> parser.parse_args(['--', '-f'])
   Namespace(args=['-f'], n=None)

   >>> # ambiguous, so the -n option greedily accepts arguments
   >>> parser.parse_args(['-n', '1', '2', '3'])
   Namespace(args=[], n=['1', '2', '3'])

   >>> parser.parse_args(['-n', '1', '--', '2', '3'])
   Namespace(args=['2', '3'], n=['1'])


Widersprüchliche Optionen
-------------------------

Bisher haben wir mit zwei Methoden einer
"argparse.ArgumentParser"-Instanz gearbeitet. Führen wir eine dritte
ein: "add_mutually_exclusive_group()". Damit können wir Optionen
angeben, die einander widersprechen. Ändern wir außerdem den Rest des
Programms, damit die neue Funktionalität mehr Sinn ergibt: Wir führen
die Option "--quiet" ein, die das Gegenteil von "--verbose" sein wird:

   import argparse

   parser = argparse.ArgumentParser()
   group = parser.add_mutually_exclusive_group()
   group.add_argument("-v", "--verbose", action="store_true")
   group.add_argument("-q", "--quiet", action="store_true")
   parser.add_argument("x", type=int, help="the base")
   parser.add_argument("y", type=int, help="the exponent")
   args = parser.parse_args()
   answer = args.x**args.y

   if args.quiet:
       print(answer)
   elif args.verbose:
       print(f"{args.x} to the power {args.y} equals {answer}")
   else:
       print(f"{args.x}^{args.y} == {answer}")

Unser Programm ist jetzt einfacher, und wir haben zur Vorführung etwas
Funktionalität eingebüßt. Wie auch immer, hier die Ausgabe:

   $ python prog.py 4 2
   4^2 == 16
   $ python prog.py 4 2 -q
   16
   $ python prog.py 4 2 -v
   4 to the power 2 equals 16
   $ python prog.py 4 2 -vq
   usage: prog.py [-h] [-v | -q] x y
   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
   $ python prog.py 4 2 -v --quiet
   usage: prog.py [-h] [-v | -q] x y
   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose

Das sollte leicht nachzuvollziehen sein. Ich habe diese letzte Ausgabe
ergänzt, damit du siehst, welche Art von Spielraum du bekommst – also
lange mit kurzen Formen von Optionen zu mischen.

Bevor wir schließen: Du willst deinen Benutzern wahrscheinlich den
Hauptzweck deines Programms mitteilen, falls sie ihn nicht kennen:

   import argparse

   parser = argparse.ArgumentParser(description="calculate X to the power of Y")
   group = parser.add_mutually_exclusive_group()
   group.add_argument("-v", "--verbose", action="store_true")
   group.add_argument("-q", "--quiet", action="store_true")
   parser.add_argument("x", type=int, help="the base")
   parser.add_argument("y", type=int, help="the exponent")
   args = parser.parse_args()
   answer = args.x**args.y

   if args.quiet:
       print(answer)
   elif args.verbose:
       print(f"{args.x} to the power {args.y} equals {answer}")
   else:
       print(f"{args.x}^{args.y} == {answer}")

Beachte den kleinen Unterschied im Verwendungstext. Beachte das "[-v |
-q]", das uns sagt, dass wir entweder "-v" oder "-q" verwenden können,
aber nicht beides gleichzeitig:

   $ python prog.py --help
   usage: prog.py [-h] [-v | -q] x y

   calculate X to the power of Y

   positional arguments:
     x              the base
     y              the exponent

   options:
     -h, --help     show this help message and exit
     -v, --verbose
     -q, --quiet


Wie man die Ausgabe von argparse übersetzt
==========================================

Die Ausgabe des Moduls "argparse" – etwa sein Hilfetext und seine
Fehlermeldungen – ist über das Modul "gettext" vollständig übersetzbar
gemacht. Damit können Anwendungen die von "argparse" erzeugten
Meldungen mühelos lokalisieren. Siehe auch Internationalizing your
programs and modules.

In dieser Ausgabe von "argparse" zum Beispiel:

   $ python prog.py --help
   usage: prog.py [-h] [-v | -q] x y

   calculate X to the power of Y

   positional arguments:
     x              the base
     y              the exponent

   options:
     -h, --help     show this help message and exit
     -v, --verbose
     -q, --quiet

Die Zeichenketten "usage:", "positional arguments:", "options:" und
"show this help message and exit" sind alle übersetzbar.

Um diese Zeichenketten zu übersetzen, müssen sie zuerst in eine
".po"-Datei herausgezogen werden. Führe dazu zum Beispiel mit Babel
diesen Befehl aus:

   $ pybabel extract -o messages.po /usr/lib/python3.12/argparse.py

Dieser Befehl zieht alle übersetzbaren Zeichenketten aus dem Modul
"argparse" heraus und gibt sie in eine Datei namens "messages.po" aus.
Der Befehl geht davon aus, dass deine Python-Installation in
"/usr/lib" liegt.

Wo das Modul "argparse" auf deinem System liegt, findest du mit diesem
Skript heraus:

   import argparse
   print(argparse.__file__)

Sobald die Meldungen in der ".po"-Datei übersetzt und die
Übersetzungen über "gettext" installiert sind, kann "argparse" die
übersetzten Meldungen anzeigen.

Um deine eigenen Zeichenketten in der Ausgabe von "argparse" zu
übersetzen, verwende "gettext".


Eigene Typumwandler
===================

Das Modul "argparse" erlaubt es dir, für deine Befehlszeilenargumente
eigene Typumwandler anzugeben. Damit kannst du die Benutzereingabe
verändern, bevor sie im "argparse.Namespace" gespeichert wird. Das
kann nützlich sein, wenn du die Eingabe vorverarbeiten musst, bevor
sie in deinem Programm verwendet wird.

Als eigenen Typumwandler kannst du jedes aufrufbare Objekt verwenden,
das ein einzelnes Zeichenkettenargument (den Argumentwert) nimmt und
den umgewandelten Wert zurückgibt. Musst du allerdings komplexere
Fälle behandeln, kannst du stattdessen über den Parameter **action**
eine eigene Aktionsklasse verwenden.

Nehmen wir zum Beispiel an, du willst Argumente mit unterschiedlichen
Präfixen behandeln und entsprechend verarbeiten:

   import argparse

   parser = argparse.ArgumentParser(prefix_chars='-+')

   parser.add_argument('-a', metavar='<value>', action='append',
                       type=lambda x: ('-', x))
   parser.add_argument('+a', metavar='<value>', action='append',
                       type=lambda x: ('+', x))

   args = parser.parse_args()
   print(args)

Ausgabe:

   $ python prog.py -a value1 +a value2
   Namespace(a=[('-', 'value1'), ('+', 'value2')])

In diesem Beispiel haben wir:

* einen Parser mit eigenen Präfixzeichen über den Parameter
  "prefix_chars" erzeugt.

* zwei Argumente definiert, "-a" und "+a", die über den Parameter
  "type" eigene Typumwandler nutzen, um den Wert zusammen mit dem
  Präfix in einem Tupel zu speichern.

Ohne die eigenen Typumwandler hätten die Argumente "-a" und "+a" als
dasselbe Argument behandelt, was unerwünscht gewesen wäre. Mit eigenen
Typumwandlern konnten wir die beiden Argumente unterscheiden.


Fazit
=====

Das Modul "argparse" bietet viel mehr als hier gezeigt. Seine
Dokumentation ist recht detailliert und ausführlich und voller
Beispiele. Nachdem du dieses Tutorial durchgearbeitet hast, solltest
du sie mühelos verdauen können, ohne dich überfordert zu fühlen.
