Saya memiliki aplikasi Python yang membutuhkan beberapa (~ 30) parameter konfigurasi. Hingga saat ini, saya menggunakan kelas OptionParser untuk menentukan nilai default dalam aplikasi itu sendiri, dengan kemungkinan untuk mengubah parameter individu pada baris perintah saat menjalankan aplikasi.
Sekarang saya ingin menggunakan file konfigurasi yang 'tepat', misalnya dari kelas ConfigParser. Pada saat yang sama, pengguna tetap dapat mengubah parameter individu di baris perintah.
Saya bertanya-tanya apakah ada cara untuk menggabungkan dua langkah, misalnya menggunakan optparse (atau argparse yang lebih baru) untuk menangani opsi baris perintah, tetapi membaca nilai default dari file konfigurasi dalam sintaks ConfigParse.
Ada ide bagaimana melakukan ini dengan cara yang mudah? Saya tidak terlalu suka menggunakan ConfigParse secara manual, dan kemudian secara manual mengatur semua default dari semua opsi ke nilai yang sesuai ...
sumber
Jawaban:
Saya baru tahu Anda bisa melakukan ini dengan
argparse.ArgumentParser.parse_known_args()
. Mulailah dengan menggunakanparse_known_args()
untuk mengurai file konfigurasi dari baris perintah, lalu membacanya dengan ConfigParser dan menyetel defaultnya, lalu mengurai sisa opsi denganparse_args()
. Ini akan memungkinkan Anda untuk memiliki nilai default, menimpanya dengan file konfigurasi dan kemudian menimpanya dengan opsi baris perintah. Misalnya:Default tanpa masukan pengguna:
$ ./argparse-partial.py Option is "default"
Default dari file konfigurasi:
$ cat argparse-partial.config [Defaults] option=Hello world! $ ./argparse-partial.py -c argparse-partial.config Option is "Hello world!"
Default dari file konfigurasi, diganti dengan baris perintah:
$ ./argparse-partial.py -c argparse-partial.config --option override Option is "override"
argprase-paration.py berikut. Agak rumit untuk menangani
-h
bantuan dengan benar.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())
sumber
another=%(option)s you are cruel
makaanother
akan selalu diselesaikanHello world you are cruel
bahkan jika diganti ke barisoption
lain di baris perintah .. argghh-parser!--version
opsi ke aplikasi Anda, lebih baik menambahkannyaconf_parser
daripada keparser
dan keluar dari aplikasi setelah bantuan pencetakan. Jika Anda menambahkan--version
untukparser
dan Anda memulai aplikasi dengan--version
bendera, dari aplikasi Anda sia-sia mencoba untuk membuka dan parseargs.conf_file
file konfigurasi (yang dapat mengalami kelainan bentuk atau bahkan tidak ada, yang mengarah ke pengecualian).Lihat ConfigArgParse - ini adalah paket PyPI baru ( open source ) yang berfungsi sebagai pengganti argparse dengan dukungan tambahan untuk file konfigurasi dan variabel lingkungan.
sumber
Saya menggunakan ConfigParser dan argparse dengan sub-perintah untuk menangani tugas-tugas semacam itu. Baris penting dalam kode di bawah ini adalah:
subp.set_defaults(**dict(conffile.items(subn)))
Ini akan menyetel default dari subcommand (dari argparse) ke nilai di bagian file konfigurasi.
Contoh yang lebih lengkap ada di bawah ini:
####### 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')
sumber
Saya tidak bisa mengatakan itu cara terbaik, tetapi saya memiliki kelas OptionParser yang saya buat yang melakukan hal itu - bertindak seperti optparse.OptionParser dengan default yang berasal dari bagian file konfigurasi. Kamu dapat memilikinya...
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))
Jangan ragu untuk menelusuri sumbernya . Pengujian berada dalam direktori saudara.
sumber
Anda dapat menggunakan ChainMap
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.
Anda dapat menggabungkan nilai dari baris perintah, variabel lingkungan, file konfigurasi, dan jika nilainya tidak ada, tentukan nilai default.
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'
sumber
dicts
diperbarui dalam urutan prioritas yang diinginkan? Dengandefaultdict
kemungkinan keuntungan karena opsi baru atau tidak didukung dapat diatur tetapi itu terpisah dariChainMap
. Saya menganggap saya melewatkan sesuatu.Pembaruan: Jawaban ini masih bermasalah; misalnya, tidak dapat menangani
required
argumen, dan membutuhkan sintaks konfigurasi yang canggung. Sebaliknya, ConfigArgParse tampaknya persis seperti yang diminta pertanyaan ini, dan merupakan pengganti drop-in yang transparan.Satu masalah dengan arus ini adalah tidak akan terjadi kesalahan jika argumen dalam file konfigurasi tidak valid. Ini adalah versi dengan sisi negatif yang berbeda: Anda harus menyertakan awalan
--
atau-
di tombol.Berikut kode python ( tautan inti dengan lisensi MIT):
# 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)
Berikut contoh file konfigurasi:
# Filename: config_correct.conf [Defaults] --arg1=Hello! --arg2=3
Sekarang, lari
> python main.py --config_file config_correct.conf --arg1 override Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
Namun, jika file konfigurasi kami mengalami kesalahan:
# config_invalid.conf --arg1=Hello! --arg2='not an integer!'
Menjalankan skrip akan menghasilkan kesalahan, seperti yang diinginkan:
> 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!'
Kelemahan utama adalah bahwa ini menggunakan
parser.parse_args
agak sembarangan untuk mendapatkan pemeriksaan kesalahan dari ArgumentParser, tetapi saya tidak mengetahui alternatif apa pun untuk ini.sumber
fromfile_prefix_chars
Mungkin bukan API yang sempurna, tetapi perlu diketahui.
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())
Kemudian:
$ 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')
Dokumentasi: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Diuji pada Python 3.6.5, Ubuntu 18.04.
sumber
Cobalah cara ini
# 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))
Gunakan:
parser.add_argument("-C", "--config", action=LoadConfigAction) parser.add_argument("-H", "--host", dest="host")
Dan buat contoh config:
# Example config: /etc/myservice.conf import os host = os.getenv("HOST_NAME", "localhost")
sumber