Was ist der beste Weg, um Befehlszeilenargumente zu analysieren? [geschlossen]

251

Was ist die einfachste , knappste und flexibelste Methode oder Bibliothek zum Parsen von Python-Befehlszeilenargumenten?

Kamens
quelle

Antworten:

183

Diese Antwort schlägt vor, optparsewelche für ältere Python-Versionen geeignet ist. Für Python 2.7 und höher, argparseersetzt optparse. Weitere Informationen finden Sie in dieser Antwort .

Wie andere Leute betonten, ist es besser, mit optparse über getopt zu gehen. getopt ist so ziemlich eine Eins-zu-Eins-Zuordnung der Standardfunktionen der getopt (3) C-Bibliothek und nicht sehr einfach zu bedienen.

optparse ist zwar etwas ausführlicher, aber viel besser strukturiert und später einfacher zu erweitern.

Hier ist eine typische Zeile, um Ihrem Parser eine Option hinzuzufügen:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

Es spricht so ziemlich für sich selbst; Zur Verarbeitungszeit akzeptiert es -q oder --query als Optionen, speichert das Argument in einem Attribut namens query und hat einen Standardwert, wenn Sie ihn nicht angeben. Es ist auch selbstdokumentierend, dass Sie das Hilfeargument (das bei Ausführung mit -h / - help verwendet wird) genau dort mit der Option deklarieren.

Normalerweise analysieren Sie Ihre Argumente mit:

options, args = parser.parse_args()

Dadurch werden standardmäßig die an das Skript übergebenen Standardargumente analysiert (sys.argv [1:]).

options.query wird dann auf den Wert gesetzt, den Sie an das Skript übergeben haben.

Sie erstellen einen Parser einfach dadurch

parser = optparse.OptionParser()

Dies sind alle Grundlagen, die Sie benötigen. Hier ist ein vollständiges Python-Skript, das dies zeigt:

import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5 Python-Zeilen, die Ihnen die Grundlagen zeigen.

Speichern Sie es in sample.py und führen Sie es einmal mit aus

python sample.py

und einmal mit

python sample.py --query myquery

Darüber hinaus werden Sie feststellen, dass Optparse sehr einfach zu erweitern ist. In einem meiner Projekte habe ich eine Befehlsklasse erstellt, mit der Sie Unterbefehle einfach in einem Befehlsbaum verschachteln können. Optparse wird häufig verwendet, um Befehle miteinander zu verketten. Es ist nicht einfach in ein paar Zeilen zu erklären, aber Sie können in meinem Repository nach der Hauptklasse sowie einer Klasse, die sie verwendet, und dem Optionsparser suchen

Thomas Vander Stichele
quelle
9
Diese Antwort ist wunderbar klar und leicht zu befolgen - für Python 2.3 bis 2.6. Für Python 2.7+ ist dies nicht die beste Antwort, da argparse jetzt Teil der Standardbibliothek ist und optparse nicht mehr verfügbar ist.
Matt Wilkie
In meinem Fall möchte ich meine Anwendung profilieren, um die Langsamkeit zu erkennen. Es gibt ein anderes Tool namens [Thunfisch] ( github.com/nschloe/tuna ), mit dem ich die gesamte Anwendung durch einfaches Hinzufügen von agrs profilieren kann, -mcProfile -o program.profaber agrparcer erfasst diese Argumente. Wie übergebe ich diese Argumente an Python Exe?
Yogeshwar
231

argparseist der Weg zu gehen. Hier ist eine kurze Zusammenfassung der Verwendung:

1) Initialisieren

import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2) Argumente hinzufügen

# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3) Analysieren

args = parser.parse_args()

4) Zugang

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5) Prüfwerte

if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

Verwendung

Richtige Verwendung:

$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

Falsche Argumente:

$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

Volle Hilfe:

$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch
Andrzej Pronobis
quelle
10
Dies ist sehr kurz und nützlich und hier ist das offizielle Dokument (der Einfachheit halber
Christophe Roussy
1
Wenn Sie argparse zu ausführlich finden, verwenden Sie stattdessen plac .
Nimitz14
76

Docopt verwenden

Seit 2012 gibt es ein sehr einfaches, leistungsstarkes und wirklich cooles Modul zum Parsen von Argumenten namens docopt . Hier ist ein Beispiel aus der Dokumentation:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

Das ist es also: 2 Zeilen Code plus Ihre doc String, ist wesentlich und Sie erhalten Ihre Argumente in Ihren Argumenten Objekt analysiert und zur Verfügung.

Mit Python-Feuer

Seit 2017 gibt es ein weiteres cooles Modul namens Python-Fire . Es kann eine CLI-Schnittstelle für Ihren Code generieren, während Sie keine Argumente analysieren. Hier ist ein einfaches Beispiel aus der Dokumentation (dieses kleine Programm stellt die Funktion doubleder Befehlszeile zur Verfügung):

import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

Über die Befehlszeile können Sie Folgendes ausführen:

> calculator.py double 10
20
> calculator.py double --number=15
30
ndemou
quelle
4
Wie braucht docopt "keine Installation"? Da es sich um ein Python-Modul handelt, muss es installiert werden. 'ImportError: Kein Modul namens docopt'
scharf
1
@keen es ist sicher nicht in Python enthalten, aber Sie müssen es nicht installieren: "Sie können einfach die Datei docopt.py in Ihr Projekt einfügen - es ist in sich geschlossen" - github.com/docopt/docopt
ndemou
9
Wir haben nur unterschiedliche Definitionen von install - und ich wollte zukünftige Leser darauf hinweisen.
scharf
1
@keen Ich habe einen Hinweis zu "keine Installation" für Personen hinzugefügt, die Ihre Definition teilen :-)
ndemou
39

Der neue Hüftweg ist argparseaus diesen Gründen. argparse> optparse> getopt

Update: Ab py2.7 ist argparse Teil der Standardbibliothek und optparse ist veraltet.

Silfheed
quelle
Ihr Hauptlink ist 404, daher habe ich ihn durch einen Link zu einer SO-Frage ersetzt, die sich mit demselben Thema befasst.
Joe Holloway
28

Ich bevorzuge Click . Es abstrahiert Verwaltungsoptionen und ermöglicht "(...) das Erstellen schöner Befehlszeilenschnittstellen auf komponierbare Weise mit so wenig Code wie nötig".

Hier ist ein Beispiel für die Verwendung:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

Außerdem werden automatisch gut formatierte Hilfeseiten generiert:

$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.
suda
quelle
14

So ziemlich jeder benutzt getopt

Hier ist der Beispielcode für das Dokument:

import getopt, sys

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    output = None
    verbose = False
    for o, a in opts:
        if o == "-v":
            verbose = True
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-o", "--output"):
            output = a

Mit einem Wort, hier ist, wie es funktioniert.

Sie haben zwei Arten von Optionen. Diejenigen, die Argumente erhalten, und diejenigen, die wie Schalter sind.

sys.argvist so ziemlich Ihr char** argvin C. Wie in C überspringen Sie das erste Element, das der Name Ihres Programms ist, und analysieren nur die Argumente:sys.argv[1:]

Getopt.getopt analysiert es gemäß der Regel, die Sie im Argument angeben.

"ho:v"Hier werden die kurzen Argumente beschrieben : -ONELETTER. Das :bedeutet das-o ein Argument akzeptiert wird.

Schließlich ["help", "output="]lange Argumente beschreibt ( --MORETHANONELETTER). Das= After-Ausgabe bedeutet erneut, dass die Ausgabe ein Argument akzeptiert.

Das Ergebnis ist eine Liste von Paaren (Option, Argument)

Wenn eine Option kein Argument akzeptiert (wie --helphier), ist das argTeil eine leere Zeichenfolge. In der Regel möchten Sie dann diese Liste durchlaufen und den Optionsnamen wie im Beispiel testen.

Ich hoffe das hat dir geholfen.

Fulmicoton
quelle
6
Mit der Ablehnung getoptin neueren Versionen von Python ist diese Antwort veraltet.
Shuttle87
1
@ shuttle87 Ab Python3.7.2 getoptist es immer noch nicht veraltet ... In der Dokumentation heißt es jedoch, dass es hauptsächlich für Benutzer bereitgestellt wird, die mit der C- getopt()Funktion vertraut sind , und dass andere Benutzer argparsemöglicherweise eine bessere Lösung sind, um "weniger Code schreiben und abrufen zu können" bessere Hilfe und Fehlermeldungen ".
Skippy le Grand Gourou
14

Verwendung, optparsedie mit der Standardbibliothek geliefert wird. Beispielsweise:

#!/usr/bin/env python
import optparse

def main():
  p = optparse.OptionParser()
  p.add_option('--person', '-p', default="world")
  options, arguments = p.parse_args()
  print 'Hello %s' % options.person

if __name__ == '__main__':
  main()

Quelle: Verwenden von Python zum Erstellen von UNIX-Befehlszeilentools

Ab Python 2.7 ist optparse jedoch veraltet. Weitere Informationen finden Sie unter: Warum argparse anstelle von optparse verwenden?

Corey
quelle
6

Für den Fall , müssen Sie möglicherweise auf, das helfen kann , wenn Sie benötigen greifen Unicode - Argumente auf Win32 (2K, XP etc.):


from ctypes import *

def wmain(argc, argv):
    print argc
    for i in argv:
        print i
    return 0

def startup():
    size = c_int()
    ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
    ref = c_wchar_p * size.value
    raw = ref.from_address(ptr)
    args = [arg for arg in raw]
    windll.kernel32.LocalFree(ptr)
    exit(wmain(len(args), args))
startup()
Shadow2531
quelle
Danke dir. Dieses Skript hat mir geholfen, einige wirklich komplizierte Zitate zu erarbeiten, die ich beim Übergeben von Startbefehlen an GVim ausführen musste.
Telotortium
6

Die Standardeinstellungen für Lightweight-Befehlszeilenargumente

Obwohl argparsees großartig ist und die richtige Antwort für vollständig dokumentierte Befehlszeilenoptionen und erweiterte Funktionen ist, können Sie die Standardeinstellungen für Funktionsargumente verwenden, um einfache Positionsargumente sehr einfach zu handhaben.

import sys

def get_args(name='default', first='a', second=2):
    return first, int(second)

first, second = get_args(*sys.argv)
print first, second

Das Argument 'name' erfasst den Skriptnamen und wird nicht verwendet. Die Testausgabe sieht folgendermaßen aus:

> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

Für einfache Skripte, bei denen ich nur einige Standardwerte möchte, finde ich dies völlig ausreichend. Möglicherweise möchten Sie auch einen Typzwang in die Rückgabewerte aufnehmen, oder Befehlszeilenwerte sind alle Zeichenfolgen.

Simon Hibbs
quelle
2
Die Anführungszeichen stimmen in der def-Anweisung nicht überein.
historystamp
3

Ich bevorzuge optparse gegenüber getopt. Es ist sehr aussagekräftig: Sie geben die Namen der Optionen und die Auswirkungen an, die sie haben sollten (z. B. Festlegen eines Booleschen Felds), und Sie erhalten ein Wörterbuch zurück, das gemäß Ihren Spezifikationen gefüllt ist.

http://docs.python.org/lib/module-optparse.html

Chris Conway
quelle
3

Ich denke, der beste Weg für größere Projekte ist optparse, aber wenn Sie nach einem einfachen Weg suchen, ist http://werkzeug.pocoo.org/documentation/script vielleicht etwas für Sie.

from werkzeug import script

# actions go here
def action_foo(name=""):
    """action foo does foo"""
    pass

def action_bar(id=0, title="default title"):
    """action bar does bar"""
    pass

if __name__ == '__main__':
    script.run()

Grundsätzlich ist also jede Funktion action_ * der Befehlszeile ausgesetzt und eine nette Hilfemeldung wird kostenlos generiert.

python foo.py 
usage: foo.py <action> [<options>]
       foo.py --help

actions:
  bar:
    action bar does bar

    --id                          integer   0
    --title                       string    default title

  foo:
    action foo does foo

    --name                        string
Peter Hoffmann
quelle
Ich habe ein kleines Paket mit automatischer Argumenterstellung entwickelt : declarative_parser. Wenn man mit werkzeug arbeitet, ist es natürlich besser, das zu behalten werkzung.script. Wie auch immer, ich bin ein großer Fan eines solchen Ansatzes.
Krassowski
3

Argparse-Code kann länger sein als der tatsächliche Implementierungscode!

Das ist ein Problem, das ich bei den meisten gängigen Optionen zum Parsen von Argumenten finde: Wenn Ihre Parameter nur bescheiden sind, wird der Code, um sie zu dokumentieren, zum Nutzen, den sie bieten, unverhältnismäßig groß.

Ein relativer Neuling in der Argument-Parsing-Szene (glaube ich) ist plac .

Es macht einige anerkannte Kompromisse mit Argparse, verwendet jedoch Inline-Dokumentation und umschließt einfach die Typfunktionsfunktion main():

def main(excel_file_path: "Path to input training file.",
     excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
     existing_model_path: "Path to an existing model to refine."=None,
     batch_size_start: "The smallest size of any minibatch."=10.,
     batch_size_stop:  "The largest size of any minibatch."=250.,
     batch_size_step:  "The step for increase in minibatch size."=1.002,
     batch_test_steps: "Flag.  If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."

    pass # Implementation code goes here!

if __name__ == '__main__':
    import plac; plac.call(main)
QA-Kollektiv
quelle
Informationspunkt: Die sauberste Verwendung von plac (wie im Beispiel gezeigt) gilt nur für Python 3.x, da 3.x-Funktionsanmerkungen verwendet werden.
Scheune
1

consoleargs verdient es, hier erwähnt zu werden. Es ist sehr einfach zu bedienen. Hör zu:

from consoleargs import command

@command
def main(url, name=None):
  """
  :param url: Remote URL 
  :param name: File name
  """
  print """Downloading url '%r' into file '%r'""" % (url, name)

if __name__ == '__main__':
  main()

Jetzt in der Konsole:

% python demo.py --help
Usage: demo.py URL [OPTIONS]

URL:    Remote URL 

Options:
    --name -n   File name

% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'

% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
Stengel
quelle
Ich habe einen ähnlichen Ansatz im deklarativen Parser verwendet , siehe Argumente Abzug (Typisierung, Dokumentzeichenfolgen, kwargs) in Dokumenten. Hauptunterschiede: Python3, Typhinweise, Pip-installierbar.
Krassowski
1
Letzte Verpflichtung im Jahr 2012
Boris
0

Hier ist eine Methode, keine Bibliothek, die für mich zu funktionieren scheint.

Die Ziele hier sind knapp zu sein, jedes Argument wird durch eine einzelne Zeile analysiert, die Argumente werden zur besseren Lesbarkeit angezeigt, der Code ist einfach und hängt nicht von speziellen Modulen ab (nur os + sys), warnt vor fehlenden oder unbekannten Argumenten Verwenden Sie eine einfache for / range () -Schleife und arbeiten Sie mit Python 2.x und 3.x.

Dargestellt sind zwei Umschaltflags (-d, -v) und zwei durch Argumente gesteuerte Werte (-i xxx und -o xxx).

import os,sys

def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"

    # Parse command line
    skip = 0
    for i in range(1, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))

Das Ziel von NextArg () ist es, das nächste Argument zurückzugeben, während nach fehlenden Daten gesucht wird, und 'überspringen' die Schleife zu überspringen, wenn NextArg () verwendet wird, wobei das Flag auf einen Liner reduziert wird.

erco
quelle
0

Ich habe den Ansatz von Erco erweitert, um erforderliche Positionsargumente und optionale Argumente zu berücksichtigen. Diese sollten vor den Argumenten -d, -v usw. stehen.

Positions- und optionale Argumente können mit PosArg (i) bzw. OptArg (i, Standard) abgerufen werden. Wenn ein optionales Argument gefunden wird, wird die Startposition für die Suche nach Optionen (z. B. -i) um 1 nach vorne verschoben, um zu vermeiden, dass ein "unerwarteter" Todesfall auftritt.

import os,sys


def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

def PosArg(i):
    '''Return positional argument'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return sys.argv[i]

def OptArg(i, default):
    '''Return optional argument (if there is one)'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    if sys.argv[i][:1] != '-':
        return True, sys.argv[i]
    else:
        return False, default


### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"
    options_start = 3

    # --- Parse two positional parameters ---
    n1 = int(PosArg(1))
    n2 = int(PosArg(2))

    # --- Parse an optional parameters ---
    present, a3 = OptArg(3,50)
    n3 = int(a3)
    options_start += int(present)

    # --- Parse rest of command line ---
    skip = 0
    for i in range(options_start, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("Number 1 = %d" % n1)
    print("Number 2 = %d" % n2)
    print("Number 3 = %d" % n3)
    print("Debug    = %d" % debug)
    print("verbose  = %d" % verbose)
    print("infile   = %s" % infile)
    print("outfile  = %s" % outfile) 
Erik
quelle