पायथन में कमांड लाइन पर कॉन्फ़िगरेशन विकल्पों को ओवरराइड करने की अनुमति देने का सबसे अच्छा तरीका कौन सा है?


89

मेरे पास एक पायथन एप्लिकेशन है जिसे काफी कुछ (~ 30) कॉन्फ़िगरेशन मापदंडों की आवश्यकता है। अब तक, मैंने एप्लिकेशन में स्वयं डिफ़ॉल्ट मानों को परिभाषित करने के लिए OptionParser वर्ग का उपयोग किया, एप्लिकेशन को लागू करते समय कमांड लाइन पर व्यक्तिगत मापदंडों को बदलने की संभावना के साथ।

अब मैं 'उचित' विन्यास फाइल का उपयोग करना चाहूंगा, उदाहरण के लिए विन्यासक वर्ग से। उसी समय, उपयोगकर्ताओं को अभी भी कमांड लाइन पर व्यक्तिगत मापदंडों को बदलने में सक्षम होना चाहिए।

मैं सोच रहा था कि क्या दो चरणों को संयोजित करने का कोई तरीका है, उदाहरण के लिए कमांड लाइन विकल्प को संभालने के लिए ऑप्टपर्स (या नया एर्गपर्स) का उपयोग करें, लेकिन configParse सिंटैक्स में एक कॉन्फ़िग फ़ाइल से डिफ़ॉल्ट मानों को पढ़ना।

किसी भी विचार कैसे एक आसान तरीके से यह करने के लिए? मैं वास्तव में कल्पना को मैन्युअल रूप से कॉन्फ़िगर नहीं करता हूं, और फिर मैन्युअल रूप से सभी विकल्पों के सभी मानों को उचित मानों पर सेट कर रहा हूं ...


6
अद्यतन : ConfigArgParse पैकेज एक ड्रॉप-इन प्रतिस्थापन के लिए है जो कि अनुमति देता है विकल्प भी विन्यास फाइल और / या पर्यावरण चर के माध्यम से सेट किया जा सकता है। नीचे दिए गए उत्तर को देखें @ user553965
nealmcb

जवाबों:


89

मुझे अभी पता चला है कि आप ऐसा कर सकते हैं argparse.ArgumentParser.parse_known_args()parse_known_args()कमांडलाइन से कॉन्फ़िगरेशन फ़ाइल को पार्स करने के लिए उपयोग करना शुरू करें , फिर इसे कॉन्फिगरेशन के साथ पढ़ें और डिफॉल्ट सेट करें, और फिर बाकी विकल्पों को पार्स करें parse_args()। यह आपको डिफ़ॉल्ट मान रखने की अनुमति देगा, कॉन्फ़िगरेशन फ़ाइल के साथ ओवरराइड करें और फिर कमांड लाइन विकल्प के साथ ओवरराइड करें। उदाहरण के लिए:

उपयोगकर्ता इनपुट के साथ डिफ़ॉल्ट:

$ ./argparse-partial.py
Option is "default"

कॉन्फ़िगरेशन फ़ाइल से डिफ़ॉल्ट:

$ cat argparse-partial.config 
[Defaults]
option=Hello world!
$ ./argparse-partial.py -c argparse-partial.config 
Option is "Hello world!"

कॉन्फ़िगरेशन फ़ाइल से डिफ़ॉल्ट, कमांडलाइन द्वारा ओवरराइड किया गया:

$ ./argparse-partial.py -c argparse-partial.config --option override
Option is "override"

argprase-partial.py इस प्रकार है। -hठीक से मदद के लिए संभालना थोड़ा जटिल है ।

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())

20
मुझे उपरोक्त कोड का पुन: उपयोग करने के लिए कहा गया है और मैं इसे जघन डोमेन में रखता हूं।
वॉन

22
'जघन डोमेन' ने मुझे हंसाया। मैं सिर्फ एक बेवकूफ बच्चा हूं।
सिल्वेनड

1
अरे! यह वास्तव में अच्छा कोड है, लेकिन SafeConfigParser कमांड लाइन द्वारा ओवरराइड की गई संपत्तियों के इंटरपोलिंग काम नहीं करता है । उदाहरण के लिए, यदि आप निम्न पंक्ति को argparse-आंशिक.config में जोड़ते हैं, another=%(option)s you are cruelतो anotherहमेशा इस बात का Hello world you are cruelभी समाधान करेंगे कि optionकमांड लाइन में कुछ और से ओवरराइड किया गया है .. argghh-parser!
.हदनी

ध्यान दें कि set_defaults केवल तभी काम करता है जब तर्क नामों में डैश या अंडरस्कोर न हों। तो कोई --myVar के बजाय --my-var (जो कि दुर्भाग्य से, काफी बदसूरत है) का विकल्प चुन सकता है। कॉन्फ़िगरेशन फ़ाइल के लिए केस-सेंसिटिविटी को सक्षम करने के लिए, फ़ाइल को पार्स करने से पहले config.optionxform = str का उपयोग करें, इसलिए myVar myvar में तब्दील नहीं होता है।
केविन बदर

1
ध्यान दें कि आप जोड़ना चाहते हैं, तो --versionआपके आवेदन करने का विकल्प है, यह करने के लिए इसे जोड़ने के लिए बेहतर है conf_parserकी तुलना में parserमुद्रण मदद के बाद और बाहर निकलने आवेदन। यदि आप जोड़ते --versionहैं parserऔर आप --versionझंडे के साथ आवेदन शुरू करते हैं, तो आपके आवेदन से अनावश्यक रूप से args.conf_fileकॉन्फ़िगरेशन फ़ाइल को खोलने और पार्स करने की कोशिश करें (जो कि विकृत या अस्तित्वहीन भी हो सकती है, जो अपवाद की ओर जाता है)।
patryk.beza

21

ConfigArgParse की जाँच करें - इसका एक नया PyPI पैकेज ( खुला स्रोत ) जो कॉन्फ़िगरेशन फ़ाइलों और पर्यावरण चर के लिए अतिरिक्त समर्थन के साथ argparse के प्रतिस्थापन में एक बूंद के रूप में कार्य करता है।


3
बस यह कोशिश की और बुद्धि महान काम करता है :) यह बाहर इशारा करने के लिए धन्यवाद।
red_tiger

2
धन्यवाद - अच्छा लग रहा है! वह वेब पेज अन्य विकल्पों के साथ कॉन्फ़िगरअगरगर्से की तुलना भी करता है, जिसमें argparse, ConfArgParse, appsettings, argparse_cnfig, yconf, hieropt, और configurati
nealmcb

9

मैं इस तरह के कार्यों को संभालने के लिए सबपांडैंड के साथ कॉन्फ़िगपार्सर और अर्गपर्स का उपयोग कर रहा हूं। नीचे दिए गए कोड में महत्वपूर्ण पंक्ति है:

subp.set_defaults(**dict(conffile.items(subn)))

यह कॉन्फ़िगरेशन फ़ाइल के अनुभाग में मानों के लिए subcommand (argparse से) के डिफ़ॉल्ट सेट करेगा।

एक और पूर्ण उदाहरण नीचे है:

####### 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')

मेरी समस्या यह है कि argparse config फाइल पथ सेट करता है, और config फ़ाइल argparse चूक सेट करता है ... बेवकूफ चिकन-अंडा समस्या
olivervbk

4

मैं यह नहीं कह सकता कि यह सबसे अच्छा तरीका है, लेकिन मेरे पास एक OptionParser वर्ग है जो मैंने बनाया जो कि बस ऐसा करता है - एक config फाइल अनुभाग से आने वाली चूक के साथ Optparse.OptionParser जैसी कार्य करता है। तुम ले लो...

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))

स्रोत को ब्राउज़ करने के लिए स्वतंत्र महसूस करें । टेस्ट एक साहसी निर्देशिका में हैं।


3

आप 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.

आप कमांड लाइन, पर्यावरण चर, कॉन्फ़िगरेशन फ़ाइल से मानों को जोड़ सकते हैं और यदि मान नहीं है, तो डिफ़ॉल्ट मान को परिभाषित नहीं किया जाता है।

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'

चैनपावर का क्या लाभ है dicts, जो पूर्ववर्ती क्रम में अपडेट की गई श्रृंखला को वांछित कहना है ? साथ defaultdictवहाँ संभवतः है एक फायदा उपन्यास या असमर्थित विकल्प सेट किया जा सकता है लेकिन यह है कि के से अलग रूप में ChainMap। मुझे लगता है कि मुझे कुछ याद आ रहा है।
दान

2

अद्यतन: इस उत्तर में अभी भी समस्याएँ हैं; उदाहरण के लिए, यह requiredतर्कों को संभाल नहीं सकता है, और एक अजीब विन्यास वाक्यविन्यास की आवश्यकता है। इसके बजाय, configArgParse को लगता है कि यह प्रश्न वास्तव में क्या पूछता है, और एक पारदर्शी, ड्रॉप-इन प्रतिस्थापन है।

वर्तमान के साथ एक समस्या यह है कि यह त्रुटि नहीं करेगा यदि कॉन्फ़िगरेशन फ़ाइल में तर्क अमान्य हैं। यहां एक अलग नकारात्मक पहलू के साथ एक संस्करण है: आपको कुंजियों में --या -उपसर्ग को शामिल करने की आवश्यकता होगी ।

यहाँ अजगर कोड ( एमआईटी लाइसेंस के साथ लिंक ):

# 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)

यहाँ एक विन्यास फाइल का उदाहरण दिया गया है:

# Filename: config_correct.conf
[Defaults]
--arg1=Hello!
--arg2=3

अब चल रहा है

> python main.py --config_file config_correct.conf --arg1 override
Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')

हालाँकि, अगर हमारी कॉन्फ़िग फ़ाइल में कोई त्रुटि है:

# config_invalid.conf
--arg1=Hello!
--arg2='not an integer!'

स्क्रिप्ट चलाना, इच्छानुसार त्रुटि उत्पन्न करेगा:

> 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!'

मुख्य नकारात्मक पक्ष यह है कि यह parser.parse_argsArgumentParser से त्रुटि जांच प्राप्त करने के लिए कुछ हद तक हैकिंग का उपयोग करता है , लेकिन मुझे इसके किसी विकल्प के बारे में पता नहीं है।


2

fromfile_prefix_chars

शायद सही एपीआई नहीं है, लेकिन इसके बारे में जानने लायक है।

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())

फिर:

$ 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')

दस्तावेज़ीकरण: https://docs.python.org/3.6/library/argparse.html#fromfile-prever-s

पायथन 3.6.5, उबंटू 18.04 पर परीक्षण किया गया।


1

इस तरह से कोशिश करें

# 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))

इसका इस्तेमाल करें:

parser.add_argument("-C", "--config", action=LoadConfigAction)
parser.add_argument("-H", "--host", dest="host")

और उदाहरण विन्यास बनाएँ:

# Example config: /etc/myservice.conf
import os
host = os.getenv("HOST_NAME", "localhost")
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.