59

コマンドラインまたは環境変数から渡すことができる特定のオプションを持つスクリプトがあります。両方が存在する場合はCLIが優先され、どちらも設定されていない場合はエラーが発生します。

解析後にオプションが割り当てられていることを確認できましたが、argparseに手間のかかる作業を行わせ、解析が失敗した場合に使用状況ステートメントを表示する責任を負わせることを好みます。

私はこれに対するいくつかの代替アプローチを考え出しました(それらは別々に議論できるように答えとして以下に投稿します)が、それらは私にはかなり不器用であり、私は何かが欠けていると思います。

これを行うための受け入れられた「最良の」方法はありますか?

(CLIオプションと環境変数の両方が設定されていない場合に、目的の動作を明確にするために編集します)

4

12 に答える 12

77

取得する環境変数を使用してdefault=、引数のofをofに.get()設定できます。os.environ

.get()呼び出しで2番目の引数を渡すこともできます。これは、.get()その名前で環境変数が見つからない場合のデフォルト値です(デフォルトではその場合に.get()返されますNone)。

import argparse
import os

parser = argparse.ArgumentParser(description='test')
parser.add_argument('--url', default=os.environ.get('URL'))

args = parser.parse_args()
if not args.url:
    exit(parser.print_usage())
于 2012-05-11T12:32:50.270 に答える
66

私はこのパターンを頻繁に使用するので、それを処理するための単純なアクションクラスをパッケージ化しました。

import argparse
import os

class EnvDefault(argparse.Action):
    def __init__(self, envvar, required=True, default=None, **kwargs):
        if not default and envvar:
            if envvar in os.environ:
                default = os.environ[envvar]
        if required and default:
            required = False
        super(EnvDefault, self).__init__(default=default, required=required, 
                                         **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)

次に、次のコードからこれを呼び出すことができます。

import argparse
from envdefault import EnvDefault

parser=argparse.ArgumentParser()
parser.add_argument(
    "-u", "--url", action=EnvDefault, envvar='URL', 
    help="Specify the URL to process (can also be specified using URL environment variable)")
args=parser.parse_args()
于 2012-05-11T12:17:53.067 に答える
32

私は通常、複数の引数(認証とAPIキー)に対してこれを行う必要があります。これは単純で簡単です。**kwargsを使用します。

def environ_or_required(key):
    return (
        {'default': os.environ.get(key)} if os.environ.get(key)
        else {'required': True}
    )

parser.add_argument('--thing', **environ_or_required('THING'))
于 2017-07-29T17:46:31.093 に答える
23

ConfigArgParseは、環境変数のサポートをargparseに追加するため、次のようなことができます。

p = configargparse.ArgParser()
p.add('-m', '--moo', help='Path of cow', env_var='MOO_PATH') 
options = p.parse_args()
于 2014-09-13T23:52:02.587 に答える
7

1つのオプションは、環境変数が設定されているかどうかを確認し、それに応じてadd_argumentの呼び出しを変更することです。

import argparse
import os

parser=argparse.ArgumentParser()
if 'CVSWEB_URL' in os.environ:
    cvsopt = { 'default': os.environ['CVSWEB_URL'] }
else:
    cvsopt = { 'required': True }
parser.add_argument(
    "-u", "--cvsurl", help="Specify url (overrides CVSWEB_URL environment variable)", 
    **cvsopt)
args=parser.parse_args()
于 2012-05-11T12:14:08.547 に答える
5

トピックはかなり古いですが、私は同様の問題を抱えていたので、私の解決策をあなたと共有したいと思いました。残念ながら、@ Russell Heillingによって提案されたカスタムアクションソリューションは、いくつかの理由で機能しません。

  • 事前定義されたアクション(のようなstore_true)を使用できなくなります
  • 入っていないdefaultときにフォールバックしたい(簡単に修正できる)envvaros.environ
  • actionor envvar(常にaction.dest.upper())を指定せずに、すべての引数に対してこの動作をしたいと思います。

これが私の解決策です(Python 3で):

class CustomArgumentParser(argparse.ArgumentParser):
    class _CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
        def _get_help_string(self, action):
            help = super()._get_help_string(action)
            if action.dest != 'help':
                help += ' [env: {}]'.format(action.dest.upper())
            return help

    def __init__(self, *, formatter_class=_CustomHelpFormatter, **kwargs):
        super().__init__(formatter_class=formatter_class, **kwargs)

    def _add_action(self, action):
        action.default = os.environ.get(action.dest.upper(), action.default)
        return super()._add_action(action)
于 2014-07-09T19:26:52.990 に答える
2

使用できますOptionParser()

from optparse import OptionParser

def argument_parser(self, parser):
    parser.add_option('--foo', dest="foo", help="foo", default=os.environ.get('foo', None))
    parser.add_option('--bar', dest="bar", help="bar", default=os.environ.get('bar', None))
    return(parser.parse_args())

parser = OptionParser()
(options, args) = argument_parser(parser)
foo = options.foo
bar = options.bar
print("foo: {}".format(foo))
print("bar: {}".format(bar))

シェル:

export foo=1
export bar=2
python3 script.py
于 2019-07-22T21:27:18.487 に答える
1

元の質問/回答が私に多くの助けを与えたので、私は私の解決策を投稿すると思いました。

私の問題はラッセルの問題とは少し異なります。OptionParserを使用しており、各引数の環境変数の代わりに、コマンドラインをシミュレートする変数が1つだけあります。

すなわち

MY_ENVIRONMENT_ARGS = --arg1 "Maltese" --arg2 "Falcon" -r "1930" -h

解決:

def set_defaults_from_environment(oparser):

    if 'MY_ENVIRONMENT_ARGS' in os.environ:

        environmental_args = os.environ[ 'MY_ENVIRONMENT_ARGS' ].split()

        opts, _ = oparser.parse_args( environmental_args )

        oparser.defaults = opts.__dict__

oparser = optparse.OptionParser()
oparser.add_option('-a', '--arg1', action='store', default="Consider")
oparser.add_option('-b', '--arg2', action='store', default="Phlebas")
oparser.add_option('-r', '--release', action='store', default='1987')
oparser.add_option('-h', '--hardback', action='store_true', default=False)

set_defaults_from_environment(oparser)

options, _ = oparser.parse_args(sys.argv[1:])

ここでは、引数が見つからなくてもエラーをスローしません。しかし、私が望むなら、私はただのようなことをすることができます

for key in options.__dict__:
    if options.__dict__[key] is None:
        # raise error/log problem/print to console/etc
于 2013-04-19T18:05:45.833 に答える
1

デフォルト、環境変数、およびコマンドライン引数をマージするユースケースChainMapの例があります。

import os, argparse

defaults = {'color': 'red', 'user': 'guest'}

parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k:v for k, v in vars(namespace).items() if v}

combined = ChainMap(command_line_args, os.environ, defaults)

美しく慣用的なPythonについての素晴らしい話から私に来ました。

ただし、小文字と大文字の辞書キーの違いについてはどうすればよいかわかりません。両方-u foobarが引数として渡され、環境がに設定されているUSER=bazbaz場合、combined辞書はのようになります{'user': 'foobar', 'USER': 'bazbaz'}

于 2018-09-30T13:57:39.390 に答える
0

defaultこれは、の名前空間引数を使用することで混乱を回避する、比較的単純(コメントが多いため長く見える)でありながら完全なソリューションですparse_args。デフォルトでは、環境変数をコマンドライン引数と同じように解析しますが、簡単に変更できます。

import shlex

# Notes:
#   * Based on https://github.com/python/cpython/blob/
#               15bde92e47e824369ee71e30b07f1624396f5cdc/
#               Lib/argparse.py
#   * Haven't looked into handling "required" for mutually exclusive groups
#   * Probably should make new attributes private even though it's ugly.
class EnvArgParser(argparse.ArgumentParser):
    # env_k:    The keyword to "add_argument" as well as the attribute stored
    #           on matching actions.
    # env_f:    The keyword to "add_argument". Defaults to "env_var_parse" if
    #           not provided.
    # env_i:    Basic container type to identify unfilled arguments.
    env_k = "env_var"
    env_f = "env_var_parse"
    env_i = type("env_i", (object,), {})

    def add_argument(self, *args, **kwargs):
        map_f = (lambda m,k,f=None,d=False:
                    (k, k in m, m.pop(k,f) if d else m.get(k,f)))

        env_k = map_f(kwargs, self.env_k, d=True, f="")
        env_f = map_f(kwargs, self.env_f, d=True, f=self.env_var_parse)

        if env_k[1] and not isinstance(env_k[2], str):
            raise ValueError(f"Parameter '{env_k[0]}' must be a string.")

        if env_f[1] and not env_k[1]:
            raise ValueError(f"Parameter '{env_f[0]}' requires '{env_k[0]}'.")

        if env_f[1] and not callable(env_f[2]):
            raise ValueError(f"Parameter '{env_f[0]}' must be callable.")

        action = super().add_argument(*args, **kwargs)

        if env_k[1] and not action.option_strings:
            raise ValueError(f"Positional parameters may not specify '{env_k[0]}'.")

        # We can get the environment now:
        #   * We need to know now if the keys exist anyway
        #   * os.environ is static
        env_v = map_f(os.environ, env_k[2], f="")

        # Examples:
        # env_k:
        #   ("env_var", True,  "FOO_KEY")
        # env_v:
        #   ("FOO_KEY", False, "")
        #   ("FOO_KEY", True,  "FOO_VALUE")
        #
        # env_k:
        #   ("env_var", False, "")
        # env_v:
        #   (""       , False, "")
        #   ("",        True,  "RIDICULOUS_VALUE")

        # Add the identifier to all valid environment variable actions for
        # later access by i.e. the help formatter.
        if env_k[1]:
            if env_v[1] and action.required:
                action.required = False
            i = self.env_i()
            i.a = action
            i.k = env_k[2]
            i.f = env_f[2]
            i.v = env_v[2]
            i.p = env_v[1]
            setattr(action, env_k[0], i)

        return action

    # Overriding "_parse_known_args" is better than "parse_known_args":
    #   * The namespace will already have been created.
    #   * This method runs in an exception handler.
    def _parse_known_args(self, arg_strings, namespace):
        """precedence: cmd args > env var > preexisting namespace > defaults"""

        for action in self._actions:
            if action.dest is argparse.SUPPRESS:
                continue
            try:
                i = getattr(action, self.env_k)
            except AttributeError:
                continue
            if not i.p:
                continue
            setattr(namespace, action.dest, i)

        namespace, arg_extras = super()._parse_known_args(arg_strings, namespace)

        for k,v in vars(namespace).copy().items():
            # Setting "env_i" on the action is more effective than using an
            # empty unique object() and mapping namespace attributes back to
            # actions.
            if isinstance(v, self.env_i):
                fv = v.f(v.a, v.k, v.v, arg_extras)
                if fv is argparse.SUPPRESS:
                    delattr(namespace, k)
                else:
                    # "_parse_known_args::take_action" checks for action
                    # conflicts. For simplicity we don't.
                    v.a(self, namespace, fv, v.k)

        return (namespace, arg_extras)

    def env_var_parse(self, a, k, v, e):
        # Use shlex, yaml, whatever.
        v = shlex.split(v)

        # From "_parse_known_args::consume_optional".
        n = self._match_argument(a, "A"*len(v))

        # From the main loop of "_parse_known_args". Treat additional
        # environment variable arguments just like additional command-line
        # arguments (which will eventually raise an exception).
        e.extend(v[n:])

        return self._get_values(a, v[:n])


# Derived from "ArgumentDefaultsHelpFormatter".
class EnvArgHelpFormatter(argparse.HelpFormatter):
    """Help message formatter which adds environment variable keys to
    argument help.
    """

    env_k = EnvArgParser.env_k

    # This is supposed to return a %-style format string for "_expand_help".
    # Since %-style strings don't support attribute access we instead expand
    # "env_k" ourselves.
    def _get_help_string(self, a):
        h = super()._get_help_string(a)
        try:
            i = getattr(a, self.env_k)
        except AttributeError:
            return h
        s = f" ({self.env_k}: {i.k})"
        if s not in h:
            h += s
        return h


# An example mix-in.
class DefEnvArgHelpFormatter\
        ( EnvArgHelpFormatter
        , argparse.ArgumentDefaultsHelpFormatter
        ):
    pass

プログラム例:

parser = EnvArgParser\
        ( prog="Test Program"
        , formatter_class=DefEnvArgHelpFormatter
        )

parser.add_argument\
        ( '--bar'
        , required=True
        , env_var="BAR"
        , type=int
        , nargs="+"
        , default=22
        , help="Help message for bar."
        )

parser.add_argument\
        ( 'baz'
        , type=int
        )

args = parser.parse_args()
print(args)

プログラム出力の例:

$ BAR="1 2 3 '45  ' 6 7" ./envargparse.py 123
Namespace(bar=[1, 2, 3, 45, 6, 7], baz=123)

$ ./envargparse.py -h
usage: Test Program [-h] --bar BAR [BAR ...] baz

positional arguments:
  baz

optional arguments:
  -h, --help           show this help message and exit
  --bar BAR [BAR ...]  Help message for bar. (default: 22) (env_var: BAR)
于 2019-06-06T16:20:40.003 に答える
0

Clickライブラリはこれを明示的に処理します。

import click

@click.command()
@click.argument('src', envvar='SRC', type=click.File('r'))
def echo(src):
    """Print value of SRC environment variable."""
    click.echo(src.read())

そしてコマンドラインから:

$ export SRC=hello.txt
$ echo
Hello World!

https://click.palletsprojects.com/en/master/arguments/#environment-variables

あなたはそれをインストールすることができます

pip install click
于 2020-04-07T16:26:15.847 に答える
0

別のオプション:

    parser = argparse.ArgumentParser()
    env = os.environ
    def add_argument(key, *args, **kwargs):
        if key in env:
            kwargs['default'] = env[key]
        parser.add_argument(*args, **kwargs)

    add_argument('--type', type=str)

またはこれは、os.getenvデフォルト値の設定に使用します。

parser = argparse.ArgumentParser()
parser.add_argument('--type', type=int, default=os.getenv('type',100))
于 2020-06-15T06:27:12.763 に答える