Ich habe eine Python-Anwendung, die einige (~ 30) Konfigurationsparameter benötigt. Bisher habe ich die OptionParser-Klasse verwendet, um Standardwerte in der App selbst zu definieren, mit der Möglichkeit, einzelne Parameter in der Befehlszeile beim Aufrufen der Anwendung zu ändern.
Jetzt möchte ich 'richtige' Konfigurationsdateien verwenden, zum Beispiel aus der ConfigParser-Klasse. Gleichzeitig sollten Benutzer weiterhin in der Lage sein, einzelne Parameter in der Befehlszeile zu ändern.
Ich habe mich gefragt, ob es eine Möglichkeit gibt, die beiden Schritte zu kombinieren, z. B. optparse (oder das neuere argparse), um Befehlszeilenoptionen zu verarbeiten, aber die Standardwerte aus einer Konfigurationsdatei in der ConfigParse-Syntax zu lesen.
Irgendwelche Ideen, wie man das auf einfache Weise macht? Ich habe keine Lust, ConfigParse manuell aufzurufen und dann alle Standardeinstellungen aller Optionen manuell auf die entsprechenden Werte zu setzen ...
quelle
Antworten:
Ich habe gerade entdeckt, dass Sie dies tun können
argparse.ArgumentParser.parse_known_args()
. Beginnen Sie damitparse_known_args()
, eine Konfigurationsdatei über die Befehlszeile zu analysieren, lesen Sie sie dann mit ConfigParser, legen Sie die Standardeinstellungen fest und analysieren Sie die restlichen Optionen mitparse_args()
. Auf diese Weise können Sie einen Standardwert festlegen, diesen mit einer Konfigurationsdatei überschreiben und diesen dann mit einer Befehlszeilenoption überschreiben. Z.B:Standard ohne Benutzereingabe:
$ ./argparse-partial.py Option is "default"
Standard aus der Konfigurationsdatei:
$ cat argparse-partial.config [Defaults] option=Hello world! $ ./argparse-partial.py -c argparse-partial.config Option is "Hello world!"
Standard aus der Konfigurationsdatei, überschrieben von der Befehlszeile:
$ ./argparse-partial.py -c argparse-partial.config --option override Option is "override"
argprase-partial.py folgt. Es ist etwas kompliziert, um
-h
Hilfe richtig zu handhaben .import argparse import ConfigParser import sys def main(argv=None): # Do argv default this way, as doing it in the functional # declaration sets it at compile time. if argv is None: argv = sys.argv # Parse any conf_file specification # We make this parser with add_help=False so that # it doesn't parse -h and print help. conf_parser = argparse.ArgumentParser( description=__doc__, # printed with -h/--help # Don't mess with format of description formatter_class=argparse.RawDescriptionHelpFormatter, # Turn off help, so we print all options in response to -h add_help=False ) conf_parser.add_argument("-c", "--conf_file", help="Specify config file", metavar="FILE") args, remaining_argv = conf_parser.parse_known_args() defaults = { "option":"default" } if args.conf_file: config = ConfigParser.SafeConfigParser() config.read([args.conf_file]) defaults.update(dict(config.items("Defaults"))) # Parse rest of arguments # Don't suppress add_help here so it will handle -h parser = argparse.ArgumentParser( # Inherit options from config_parser parents=[conf_parser] ) parser.set_defaults(**defaults) parser.add_argument("--option") args = parser.parse_args(remaining_argv) print "Option is \"{}\"".format(args.option) return(0) if __name__ == "__main__": sys.exit(main())
quelle
another=%(option)s you are cruel
dannanother
würde immer lösen ,Hello world you are cruel
auch wennoption
sonst in der Befehlszeile , um etwas außer Kraft gesetzt wird .. argghh-Parser!--version
es besser ist , eine Option zu Ihrer Anwendung hinzuzufügen,conf_parser
als sie hinzuzufügen ,parser
und die Anwendung nach dem Drucken der Hilfe zu beenden. Wenn Sie hinzufügen--version
zuparser
und starten Sie Anwendung mit--version
Flagge, als unnötig Ihre Anwendung versuchen zu öffnen und Parse -args.conf_file
Konfigurationsdatei (die malformed werden kann oder gar nicht vorhanden, was dazu führt, Ausnahme).Schauen Sie sich ConfigArgParse an - ein neues PyPI-Paket ( Open Source ), das als Ersatz für argparse dient und zusätzliche Unterstützung für Konfigurationsdateien und Umgebungsvariablen bietet.
quelle
Ich verwende ConfigParser und argparse mit Unterbefehlen, um solche Aufgaben zu erledigen. Die wichtige Zeile im folgenden Code lautet:
Dadurch werden die Standardeinstellungen des Unterbefehls (von argparse) auf die Werte im Abschnitt der Konfigurationsdatei festgelegt.
Ein vollständigeres Beispiel finden Sie unten:
####### content of example.cfg: # [sub1] # verbosity=10 # gggg=3.5 # [sub2] # host=localhost import ConfigParser import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser_sub1 = subparsers.add_parser('sub1') parser_sub1.add_argument('-V','--verbosity', type=int, dest='verbosity') parser_sub1.add_argument('-G', type=float, dest='gggg') parser_sub2 = subparsers.add_parser('sub2') parser_sub2.add_argument('-H','--host', dest='host') conffile = ConfigParser.SafeConfigParser() conffile.read('example.cfg') for subp, subn in ((parser_sub1, "sub1"), (parser_sub2, "sub2")): subp.set_defaults(**dict(conffile.items(subn))) print parser.parse_args(['sub1',]) # Namespace(gggg=3.5, verbosity=10) print parser.parse_args(['sub1', '-V', '20']) # Namespace(gggg=3.5, verbosity=20) print parser.parse_args(['sub1', '-V', '20', '-G','42']) # Namespace(gggg=42.0, verbosity=20) print parser.parse_args(['sub2', '-H', 'www.example.com']) # Namespace(host='www.example.com') print parser.parse_args(['sub2',]) # Namespace(host='localhost')
quelle
Ich kann nicht sagen, dass dies der beste Weg ist, aber ich habe eine OptionParser-Klasse erstellt, die genau das tut - verhält sich wie optparse.OptionParser mit Standardeinstellungen aus einem Konfigurationsdateibereich. Du kannst es haben...
class OptionParser(optparse.OptionParser): def __init__(self, **kwargs): import sys import os config_file = kwargs.pop('config_file', os.path.splitext(os.path.basename(sys.argv[0]))[0] + '.config') self.config_section = kwargs.pop('config_section', 'OPTIONS') self.configParser = ConfigParser() self.configParser.read(config_file) optparse.OptionParser.__init__(self, **kwargs) def add_option(self, *args, **kwargs): option = optparse.OptionParser.add_option(self, *args, **kwargs) name = option.get_opt_string() if name.startswith('--'): name = name[2:] if self.configParser.has_option(self.config_section, name): self.set_default(name, self.configParser.get(self.config_section, name))
Fühlen Sie sich frei, die Quelle zu durchsuchen . Tests befinden sich in einem Geschwisterverzeichnis.
quelle
Sie können ChainMap verwenden
A ChainMap groups multiple dicts or other mappings together to create a single, updateable view. If no maps are specified, a single empty dictionary is provided so that a new chain always has at least one mapping.
Sie können Werte aus der Befehlszeile, Umgebungsvariablen und der Konfigurationsdatei kombinieren und, falls der Wert nicht vorhanden ist, einen Standardwert definieren.
import os from collections import ChainMap, defaultdict options = ChainMap(command_line_options, os.environ, config_file_options, defaultdict(lambda: 'default-value')) value = options['optname'] value2 = options['other-option'] print(value, value2) 'optvalue', 'default-value'
quelle
dicts
die in der gewünschten Rangfolge aktualisiert wurde? Mitdefaultdict
gibt es möglicherweise einen Vorteil, da neuartige oder nicht unterstützte Optionen eingestellt werden können, aber das ist getrennt vonChainMap
. Ich nehme an, ich vermisse etwas.Update: Diese Antwort hat immer noch Probleme. Beispielsweise kann es keine
required
Argumente verarbeiten und erfordert eine umständliche Konfigurationssyntax. Stattdessen scheint ConfigArgParse genau das zu sein, was diese Frage verlangt, und ist ein transparenter Ersatz.Ein Problem mit dem aktuellen ist, dass es keinen Fehler gibt, wenn die Argumente in der Konfigurationsdatei ungültig sind. Hier ist eine Version mit einem anderen Nachteil: Sie müssen das Präfix
--
oder-
in die Schlüssel einfügen.Hier ist der Python-Code ( Hauptlink mit MIT-Lizenz):
# Filename: main.py import argparse import configparser if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--config_file', help='config file') args, left_argv = parser.parse_known_args() if args.config_file: with open(args.config_file, 'r') as f: config = configparser.SafeConfigParser() config.read([args.config_file]) parser.add_argument('--arg1', help='argument 1') parser.add_argument('--arg2', type=int, help='argument 2') for k, v in config.items("Defaults"): parser.parse_args([str(k), str(v)], args) parser.parse_args(left_argv, args) print(args)
Hier ist ein Beispiel für eine Konfigurationsdatei:
# Filename: config_correct.conf [Defaults] --arg1=Hello! --arg2=3
Jetzt läuft
> python main.py --config_file config_correct.conf --arg1 override Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
Wenn unsere Konfigurationsdatei jedoch einen Fehler aufweist:
# config_invalid.conf --arg1=Hello! --arg2='not an integer!'
Das Ausführen des Skripts führt wie gewünscht zu einem Fehler:
> python main.py --config_file config_invalid.conf --arg1 override usage: test_argparse_conf.py [-h] [--config_file CONFIG_FILE] [--arg1 ARG1] [--arg2 ARG2] main.py: error: argument --arg2: invalid int value: 'not an integer!'
Der Hauptnachteil ist, dass dies
parser.parse_args
etwas hackig verwendet wird, um die Fehlerprüfung von ArgumentParser zu erhalten, aber mir sind keine Alternativen dazu bekannt.quelle
Versuchen Sie es auf diese Weise
# encoding: utf-8 import imp import argparse class LoadConfigAction(argparse._StoreAction): NIL = object() def __init__(self, option_strings, dest, **kwargs): super(self.__class__, self).__init__(option_strings, dest) self.help = "Load configuration from file" def __call__(self, parser, namespace, values, option_string=None): super(LoadConfigAction, self).__call__(parser, namespace, values, option_string) config = imp.load_source('config', values) for key in (set(map(lambda x: x.dest, parser._actions)) & set(dir(config))): setattr(namespace, key, getattr(config, key))
Benutze es:
parser.add_argument("-C", "--config", action=LoadConfigAction) parser.add_argument("-H", "--host", dest="host")
Und erstellen Sie eine Beispielkonfiguration:
# Example config: /etc/myservice.conf import os host = os.getenv("HOST_NAME", "localhost")
quelle
fromfile_prefix_chars
Vielleicht nicht die perfekte API, aber wissenswert.
main.py
::#!/usr/bin/env python3 import argparse parser = argparse.ArgumentParser(fromfile_prefix_chars='@') parser.add_argument('-a', default=13) parser.add_argument('-b', default=42) print(parser.parse_args())
Dann:
$ printf -- '-a\n1\n-b\n2\n' > opts.txt $ ./main.py Namespace(a=13, b=42) $ ./main.py @opts.txt Namespace(a='1', b='2') $ ./main.py @opts.txt -a 3 -b 4 Namespace(a='3', b='4') $ ./main.py -a 3 -b 4 @opts.txt Namespace(a='1', b='2')
Dokumentation: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Getestet unter Python 3.6.5, Ubuntu 18.04.
quelle