ti-enxame.com

Analisando valores booleanos com argparse

Eu gostaria de usar argparse para analisar argumentos de linha de comando booleanos escritos como "--foo True" ou "--foo False". Por exemplo:

my_program --my_boolean_flag False

No entanto, o código de teste a seguir não faz o que eu gostaria:

import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)

Infelizmente, parsed_args.my_bool é avaliado como True. Este é o caso mesmo quando eu altero cmd_line para ["--my_bool", ""], o que é surpreendente, já que bool("") é valido para False.

Como posso fazer com que argparse parse "False", "F" e suas variantes minúsculas como False?

441
SuperElectric

Ainda outra solução usando as sugestões anteriores, mas com o erro de análise "correto" de argparse:

def str2bool(v):
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    Elif v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')

Isso é muito útil para fazer comutadores com valores padrão; por exemplo

parser.add_argument("--Nice", type=str2bool, nargs='?',
                        const=True, default=Nice,
                        help="Activate Nice mode.")

permite-me usar:

script --Nice
script --Nice <bool>

e ainda usar um valor padrão (específico para as configurações do usuário). Uma desvantagem (indiretamente relacionada) com essa abordagem é que os 'narjos' podem pegar um argumento posicional - veja esta questão relacionada e este relatório de bug argparse .

156
Maxim

Eu acho que uma maneira mais canônica de fazer isso é via:

command --feature

e

command --no-feature

argparse suporta esta versão bem:

parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Claro, se você realmente quer a versão --arg <True|False>, você poderia passar ast.literal_eval como o "tipo", ou uma função definida pelo usuário ...

def t_or_f(arg):
    ua = str(arg).upper()
    if 'TRUE'.startswith(ua):
       return True
    Elif 'FALSE'.startswith(ua):
       return False
    else:
       pass  #error condition maybe?
687
mgilson

Eu recomendo a resposta de mgilson, mas com um grupo mutuamente exclusivo
para que você não possa usar --feature e --no-feature ao mesmo tempo.

command --feature

e

command --no-feature

mas não

command --feature --no-feature

Roteiro:

feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Você pode então usar esse ajudante se for definir muitos deles:

def add_bool_arg(parser, name, default=False):
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--' + name, dest=name, action='store_true')
    group.add_argument('--no-' + name, dest=name, action='store_false')
    parser.set_defaults(**{name:default})

add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')
172
fnkr

Parece haver alguma confusão sobre o que type=bool e type='bool' podem significar. Um (ou ambos) deve significar 'executar a função bool() ou' retornar um booleano '? Como está, type='bool' não significa nada. add_argument dá um erro 'bool' is not callable, da mesma forma que você usou type='foobar' ou type='int'.

Mas argparse possui um registro que permite definir palavras-chave como esta. É usado principalmente para action, por ex. `action = 'store_true'. Você pode ver as palavras-chave registradas com:

parser._registries

que exibe um dicionário

{'action': {None: argparse._StoreAction,
  'append': argparse._AppendAction,
  'append_const': argparse._AppendConstAction,
...
 'type': {None: <function argparse.identity>}}

Existem muitas ações definidas, mas apenas um tipo, o padrão argparse.identity.

Este código define uma palavra-chave 'bool':

def str2bool(v):
  #susendberg's function
  return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool')  # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)

parser.register() não está documentado, mas também não está oculto. Na maior parte, o programador não precisa saber disso porque type e action assumem valores de função e classe. Há muitos exemplos de stackoverflow de definição de valores personalizados para ambos.


Caso não seja óbvio na discussão anterior, bool() não significa 'analisar uma string'. Da documentação do Python:

bool (x): Converte um valor para um booleano, usando o procedimento de teste de verdade padrão.

Compare isso com

int (x): Converte um número ou string x em um inteiro.

31
hpaulj

um forro:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
25
Evalds Urtans

Aqui está outra variação sem linha extra/s para definir valores padrão. O bool sempre tem um valor atribuído para que possa ser usado em instruções lógicas sem pré-verificações.

import argparse
parser = argparse.ArgumentParser(description="Parse bool")
parser.add_argument("--do-something", default=False, action="store_true" , help="Flag to do something")
args = parser.parse_args()

if args.do_something == True:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")
24
Schaki

Eu estava procurando o mesmo problema, e a solução bonita é:

def str2bool(v):
  return v.lower() in ("yes", "true", "t", "1")

e usando isso para analisar a string para booleano como sugerido acima.

18
susundberg

Além do que @mgilson disse, deve-se notar que há também um método ArgumentParser.add_mutually_exclusive_group(required=False) que tornaria trivial impor que --flag e --no-flag não sejam usados ​​ao mesmo tempo.

13
foo

Isso funciona para tudo que eu espero:

add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([])                   # Whatever the default was
parser.parse_args(['--foo'])            # True
parser.parse_args(['--nofoo'])          # False
parser.parse_args(['--foo=true'])       # True
parser.parse_args(['--foo=false'])      # False
parser.parse_args(['--foo', '--nofoo']) # Error

O código:

def _str_to_bool(s):
    """Convert string to bool (in argparse context)."""
    if s.lower() not in ['true', 'false']:
        raise ValueError('Need bool; got %r' % s)
    return {'true': True, 'false': False}[s.lower()]

def add_boolean_argument(parser, name, default=False):                                                                                               
    """Add a boolean argument to an ArgumentParser instance."""
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        '--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
    group.add_argument('--no' + name, dest=name, action='store_false')
8
Stumpy Joe Pete

Uma maneira bastante semelhante é usar:

feature.add_argument('--feature',action='store_true')

e se você definir o argumento --feature em seu comando

 command --feature

o argumento será True, se você não definir o tipo --feature o padrão dos argumentos é sempre False!

6
dl.meteo

Uma maneira mais simples seria usar como abaixo.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
5
arunkumarreddy
class FlagAction(argparse.Action):
    # From http://bugs.python.org/issue8538

    def __init__(self, option_strings, dest, default=None,
                 required=False, help=None, metavar=None,
                 positive_prefixes=['--'], negative_prefixes=['--no-']):
        self.positive_strings = set()
        self.negative_strings = set()
        for string in option_strings:
            assert re.match(r'--[A-z]+', string)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                self.positive_strings.add(positive_prefix + suffix)
            for negative_prefix in negative_prefixes:
                self.negative_strings.add(negative_prefix + suffix)
        strings = list(self.positive_strings | self.negative_strings)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, const=None, default=default, type=bool, choices=None,
                                         required=required, help=help, metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.positive_strings:
            setattr(namespace, self.dest, True)
        else:
            setattr(namespace, self.dest, False)
2
Robert T. McGibbon

Eu acho que o caminho mais canônico será:

parser.add_argument('--ensure', nargs='*', default=None)

ENSURE = config.ensure is None
1
Andreas Maertens