2010-08-31 31 views
42

Tôi có một ứng dụng Python cần một vài thông số cấu hình (~ 30). Cho đến nay, tôi đã sử dụng lớp OptionParser để định nghĩa các giá trị mặc định trong chính ứng dụng, với khả năng thay đổi các tham số riêng lẻ tại dòng lệnh khi gọi ứng dụng.Cách nào tốt nhất để cho phép các tùy chọn cấu hình được ghi đè tại dòng lệnh bằng Python?

Bây giờ tôi muốn sử dụng các tệp cấu hình 'thích hợp', ví dụ từ lớp ConfigParser. Đồng thời, người dùng vẫn có thể thay đổi các tham số riêng lẻ tại dòng lệnh.

Tôi đã tự hỏi liệu có cách nào để kết hợp hai bước, ví dụ: sử dụng optparse (hoặc argparse mới hơn) để xử lý các tùy chọn dòng lệnh, nhưng đọc các giá trị mặc định từ một tệp cấu hình trong cú pháp ConfigParse.

Bất kỳ ý tưởng nào về cách thực hiện điều này một cách dễ dàng? Tôi không thực sự ưa thích bằng tay gọi ConfigParse, và sau đó tự thiết lập tất cả các giá trị mặc định của tất cả các optinos với các giá trị thích hợp ...

Any help is appreciated rất nhiều :)

+2

** Cập nhật **: gói [ConfigArgParse] (https://pypi.python.org/pypi/ConfigArgParse) là _A thay thế cho argparse cho phép tùy chọn cũng được đặt qua tệp cấu hình và/hoặc biến môi trường._ Xem câu trả lời bên dưới theo @ user553965 – nealmcb

+0

Liên quan: [Parse config files, environment, và đối số dòng lệnh, để có được một tập hợp các tùy chọn] (https://stackoverflow.com/q/6133517/427158) – maxschlepzig

Trả lời

51

Tôi chỉ phát hiện ra bạn có thể làm điều này với argparse.ArgumentParser.parse_known_args(). Bắt đầu bằng cách sử dụng parse_known_args() để phân tích cú pháp tệp cấu hình từ dòng lệnh, sau đó đọc tệp đó bằng ConfigParser và đặt mặc định, sau đó phân tích phần còn lại của các tùy chọn với parse_args(). Điều này sẽ cho phép bạn có một giá trị mặc định, ghi đè lên đó bằng một tệp cấu hình và sau đó ghi đè lên đó bằng một tùy chọn dòng lệnh. Ví dụ:

Mặc định không có người dùng nhập vào:

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

Mặc định từ tập tin cấu hình:

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

Mặc định từ tập tin cấu hình, ghi đè bởi commandline:

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

argprase-phần .py theo sau. Hơi phức tạp khi xử lý -h để được trợ giúp đúng cách.

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

Tôi đã được yêu cầu ở trên sử dụng lại mã ở trên và tôi đặt nó vào miền pubic. – Von

+11

'miền công khai' khiến tôi cười. Tôi chỉ là một đứa trẻ ngu ngốc. – Josay

+1

argh! đây là mã thực sự thú vị, nhưng tính năng nội suy SafeConfigParser của các thuộc tính được ghi đè bởi dòng lệnh * không hoạt động *. Ví dụ. nếu bạn thêm dòng sau vào argparse-partial.config 'another =% (option) s bạn là cruel' thì' another' sẽ luôn luôn giải quyết thành 'Hello world bạn là cruel' ngay cả khi' option' được ghi đè lên cái gì khác trong dòng lệnh .. argghh-parser! – ihadanny

4

Tôi không thể nói đó là cách tốt nhất , nhưng tôi có một lớp OptionParser mà tôi đã thực hiện mà không chỉ có - hành vi như optparse.OptionParser với mặc định đến từ một phần tập tin cấu hình. Bạn có thể có nó ...

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

Feel free to browse the source. Các thử nghiệm nằm trong một thư mục anh chị em ruột.

8

Tôi đang sử dụng ConfigParser và argparse với các tiểu ban để xử lý các tác vụ như vậy. Dòng quan trọng trong mã bên dưới là:

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

Điều này sẽ đặt mặc định của tiểu nhóm (từ argparse) thành giá trị trong phần của tệp cấu hình.

Một ví dụ hoàn chỉnh hơn là dưới đây:

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

vấn đề của tôi là argparse đặt đường dẫn tệp cấu hình và tệp cấu hình đặt mặc định argparse ... vấn đề trứng gà ngu ngốc – olivervbk

7

Check-out ConfigArgParse - một gói PyPI mới của nó (open source) đóng vai trò như một thả thay thế cho argparse với sự hỗ trợ bổ sung cho các tập tin cấu hình và các biến môi trường.

+2

chỉ cần thử nó và wit hoạt động tuyệt vời :) Cảm ơn bạn đã chỉ ra điều này. –

+1

Cảm ơn - có vẻ ổn! Trang web đó cũng so sánh ConfigArgParse với các tùy chọn khác, bao gồm argparse, ConfArgParse, appsettings, argparse_cnfig, yconf, hieropt và configurati – nealmcb

0

Cố gắng theo cách này

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

Sử dụng nó:

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

Và tạo ví dụ cấu hình:

# Example config: /etc/myservice.conf 
import os 
host = os.getenv("HOST_NAME", "localhost") 
0

Cập nhật: Câu trả lời này vẫn còn có vấn đề; ví dụ: nó không thể xử lý các đối số required và yêu cầu cú pháp cấu hình khó xử. Thay vào đó, ConfigArgParse dường như chính xác là những gì câu hỏi này yêu cầu và là một thay thế thả trong suốt.

Một vấn đề với current là nó sẽ không lỗi nếu các đối số trong tệp cấu hình không hợp lệ. Dưới đây là phiên bản có nhược điểm khác: bạn cần phải bao gồm tiền tố -- hoặc - trong các phím.

Dưới đây là đoạn code python (Gist link với giấy phép 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) 

Dưới đây là một ví dụ về một tập tin cấu hình:

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

Bây giờ, chạy

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

Tuy nhiên, nếu tệp cấu hình của chúng tôi có lỗi:

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

Chạy kịch bản sẽ tạo ra một lỗi, như mong muốn:

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

Nhược điểm chính là điều này sử dụng parser.parse_args hơi hackily để có được sự kiểm tra lỗi từ ArgumentParser, nhưng tôi không biết bất cứ lựa chọn thay thế cho điều này.

Các vấn đề liên quan