Подробный уровень с argparse и несколькими параметрами -v

Я хотел бы иметь возможность указать другой уровень детализации, добавив дополнительные параметры -v в командную строку. Например:

$ myprogram.py    
$ myprogram.py -v
$ myprogram.py -vv
$ myprogram.py -v -v -v

приведет к verbose=0, verbose=1, verbose=2 и verbose=3 соответственно. Как я могу добиться этого с помощью argparse?

При желании было бы здорово также указать его как

$ myprogram -v 2

person Charles Brunet    schedule 20.05.2011    source источник
comment
Вам следует пометить ответ Бена как правильный, поскольку люди находят его с помощью поиска в Google, функции -v 2 и пользовательского фрагмента. являются непосильными для большинства пользователей.`   -  person joar    schedule 14.09.2017
comment
@joar: да, действительно. спасибо за ваш комментарий, указывающий на правильный ответ.   -  person Gyom    schedule 26.03.2019


Ответы (8)


Вы можете сделать это с помощью nargs='?' (чтобы принять аргументы 0 или 1 после флага -v) и пользовательского действия (для обработки аргументов 0 или 1):

import sys
import argparse

class VAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, const=None, 
                 default=None, type=None, choices=None, required=False, 
                 help=None, metavar=None):
        super(VAction, self).__init__(option_strings, dest, nargs, const, 
                                      default, type, choices, required, 
                                      help, metavar)
        self.values = 0
    def __call__(self, parser, args, values, option_string=None):
        # print('values: {v!r}'.format(v=values))
        if values is None:
            self.values += 1
        else:
            try:
                self.values = int(values)
            except ValueError:
                self.values = values.count('v')+1
        setattr(args, self.dest, self.values)

# test from the command line
parser = argparse.ArgumentParser()
parser.add_argument('-v', nargs='?', action=VAction, dest='verbose')
args = parser.parse_args()
print('{} --> {}'.format(sys.argv[1:], args))

print('-'*80)

for test in ['-v', '-v -v', '-v -v -v', '-vv', '-vvv', '-v 2']:
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', nargs='?', action=VAction, dest='verbose')
    args=parser.parse_args([test])
    print('{:10} --> {}'.format(test, args))

Запуск script.py -v -v из командной строки дает

['-v', '-v'] --> Namespace(verbose=2)
--------------------------------------------------------------------------------
-v         --> Namespace(verbose=1)
-v -v      --> Namespace(verbose=2)
-v -v -v   --> Namespace(verbose=3)
-vv        --> Namespace(verbose=2)
-vvv       --> Namespace(verbose=3)
-v 2       --> Namespace(verbose=2)

Раскомментируйте оператор печати, чтобы лучше видеть, что делает VAction.

person unutbu    schedule 20.05.2011
comment
пользовательское действие не требуется (больше не требуется). см. ответ от Бена. - person Brutus; 09.10.2012
comment
Очень поучительно, но... Многословность None, если не указана (может быть, ожидалось 0). Это также не очень хорошо, когда у вас есть другие варианты (а не только один), и вы хотите иметь свободный порядок аргументов. - person Tomasz Gandor; 10.10.2018
comment
К сожалению, это даже не работает. Когда вы передаете -v несколько раз в реальной командной строке, вы получаете не ['-v -v -v'], а ['-v', '-v', '-v'], и для этого ваш код дает verbose=1. - person Tomasz Gandor; 10.10.2018
comment
@TomaszGandor: Спасибо за сообщение об ошибке. Проблема с несколькими -v была исправлена. - person unutbu; 10.10.2018

argparse поддерживает action='count':

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='count', default=0)

for c in ['', '-v', '-v -v', '-vv', '-vv -v', '-v -v --verbose -vvvv']:
    print(parser.parse_args(c.split()))

Выход:

Namespace(verbose=0)
Namespace(verbose=1)
Namespace(verbose=2)
Namespace(verbose=2)
Namespace(verbose=3)
Namespace(verbose=7)

Единственная очень незначительная мелочь заключается в том, что вам нужно явно установить default=0, если вы не хотите, чтобы аргументы -v давали вам уровень детализации 0, а не None.

person Ben    schedule 01.12.2011
comment
Это не позволяет использовать синтаксис myprogram -v 2. Тем временем в документах появился action='count'. - person cfi; 28.08.2012
comment
Кажется, это лучший и самый простой ответ на вопрос ОП. - person Steve Davis; 05.04.2015
comment
Не выполняет необязательные требования к операционной системе, но для меня это действительно просто и достаточно хорошо. Спасибо! - person Enno Gröper; 19.03.2021

Вы можете ответить на первую часть вашего вопроса с помощью append_const. В противном случае вы, вероятно, застряли в написании пользовательского действия, как это предлагается в прекрасном ответ пользователя unutbu.

import argparse

ap = argparse.ArgumentParser()
ap.add_argument('-v', action = 'append_const', const = 1)

for c in ['', '-v', '-v -v', '-vv', '-vv -v']:
    opt = ap.parse_args(c.split())
    opt.v = 0 if opt.v is None else sum(opt.v)
    print opt

Выход:

Namespace(v=0)
Namespace(v=1)
Namespace(v=2)
Namespace(v=2)
Namespace(v=3)
person FMc    schedule 20.05.2011
comment
Мне нравится это решение; Я думаю, что простота кода, обеспечиваемая использованием append_const, стоит того, чтобы отказаться от -v 2. - person unutbu; 21.05.2011
comment
Выбор ответа, который я бы принял, был непростым выбором. Мне нравится простота вашего ответа. - person Charles Brunet; 22.05.2011
comment
Использование append_const также позволяет добавить аргумент -q. С dest='v', const=-1 он отменит любые -v. Я использую это с default=[2], чтобы я мог сопоставить результат с уровнями модуля ведения журнала, начиная с WARN и позволяя вам -q/v вверх и вниз по шкале. - person markpasc; 05.06.2011
comment
Вход -vv не работает, если вы установили allow_abbrev=False. Но вы все равно можете -v -v - person therealjumbo; 16.05.2019

Вот мой взгляд на это, который не использует никаких новых классов, работает как в Python 2, так и в 3 и поддерживает относительные настройки по умолчанию, используя "-v"/"--verbose" и "-q"/"--quiet" , но он не поддерживает использование чисел, например "-v 2":

#!/usr/bin/env python
import argparse
import logging
import sys

LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
DEFAULT_LOG_LEVEL = "INFO"


def main(argv):
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--verbose", "-v",
        dest="log_level",
        action="append_const",
        const=-1,
    )
    parser.add_argument(
        "--quiet", "-q",
        dest="log_level",
        action="append_const",
        const=1,
    )

    args = parser.parse_args(argv[1:])
    log_level = LOG_LEVELS.index(DEFAULT_LOG_LEVEL)

    # For each "-q" and "-v" flag, adjust the logging verbosity accordingly
    # making sure to clamp off the value from 0 to 4, inclusive of both
    for adjustment in args.log_level or ():
        log_level = min(len(LOG_LEVELS) - 1, max(log_level + adjustment, 0))

    log_level_name = LOG_LEVELS[log_level]
    print(log_level_name)
    logging.getLogger().setLevel(log_level_name)


if __name__ == "__main__":
    main(sys.argv)

Пример:

$ python2 verbosity.py -vvv
DEBUG
$ python3 verbosity.py -vvv -q
INFO
$ python2 verbosity.py -qqq -vvv -q
WARNING
$ python2 verbosity.py -qqq
CRITICAL
person Eric Pruitt    schedule 14.11.2018
comment
Мне очень понравилась эта реализация. Он очень чистый, однако, у него есть некоторые недостатки. Ваш log_level не совпадает с уровнем журнала из библиотеки журналов, поэтому их все равно нужно будет перевести. Другое дело, что уровень логирования работает наоборот, чем ниже, тем критичнее, поэтому я бы инвертировал константы из -q и -v. Теперь он работает нормально. - person MtCS; 14.06.2019
comment
@MtCS, я использовал строки по умолчанию в библиотеке журналов, поэтому все, что вам нужно сделать, это logger.setLevel(log_level_name) предполагая, что вы не используете никаких дополнительных строк. Что касается части о константах, то буквально без разницы. На мой взгляд, уровень журнала — это многословие, поэтому я использовал +1 для -v и -1 для -q, но опять же, это не имеет значения. - person Eric Pruitt; 15.06.2019

Расширяя ответ unutbu, здесь пользовательское действие, включая обработку комбинации --quiet/-q. Это проверено в Python3. Использование его в Python >= 2.7 не должно иметь большого значения.

class ActionVerbose(argparse.Action):
    def __call__(self, parser, args, values, option_string=None):
        #print(parser, args, values, option_string)
        # Obtain previously set value in case this option call is incr/decr only
        if args.verbose == None:
            base = 0
        else:
            base = args.verbose
        # One incr/decr is determined in name of option in use (--quiet/-q/-v/--verbose)
        option_string = option_string.lstrip('-')
        if option_string[0] == 'q':
            incr = -1
        elif option_string[0] == 'v':
            incr = 1
        else:
            raise argparse.ArgumentError(self,
                                         'Option string for verbosity must start with v(erbose) or q(uiet)')
        # Determine if option only or values provided
        if values==None:
            values = base + incr
        else:
            # Values might be an absolute integer verbosity level or more 'q'/'v' combinations
            try:
                values = int(values)
            except ValueError:
                values = values.lower()
                if not re.match('^[vq]+$', values):
                    raise argparse.ArgumentError(self,
                                                 "Option string for -v/-q must contain only further 'v'/'q' letters")
                values = base + incr + values.count('v') - values.count('q')
        setattr(args, self.dest, values)
    @classmethod
    def add_to_parser(cls,
                      parser, dest='verbose', default=0,
                      help_detail='(0:errors, 1:info, 2:debug)'):
        parser.add_argument('--verbose', nargs='?', action=ActionVerbose, dest=dest, metavar='level',
                            default=default,
                            help='Increase or set level of verbosity {}'.format(help_detail))
        parser.add_argument('-v',        nargs='?', action=ActionVerbose, dest=dest, metavar='level',
                            help='Increase or set level of verbosity')
        parser.add_argument('--quiet',   nargs='?', action=ActionVerbose, dest=dest, metavar='level',
                            help='Decrease or set level of verbosity')
        parser.add_argument('-q',        nargs='?', action=ActionVerbose, dest=dest, metavar='level',
                            help='Decrease or set level of verbosity')

Существует метод класса удобства, который можно использовать для настройки всех четырех обработчиков опций для --verbose, -v, -q, --quiet. Используйте это так:

parser = argparse.ArgumentParser()
ActionVerbose.add_to_parser(parser, default=defaults['verbose'])
# add more arguments here with: parser.add_argument(...)
args = parser.parse_args()

При использовании скрипта с этими аргументами вы можете сделать:

./script -vvvvvv -v 4 -v 0 -v -vvv --verbose --quiet 2 -v qqvvqvv

С этой командной строкой args.verbose будет 4.

  • Любое -v/-q/--verbose/--quiet с заданным числом является жестким, абсолютным набором args.verbose для данного числа (= уровень многословности).
  • Любое -v/--verbose без номера является приращением этого уровня.
  • Любое -q/--quiet без числа является уменьшением этого уровня.
  • За любыми -v/-q могут сразу же следовать еще v/q букв, итоговый уровень the old level + sum(count('v')) - sum(count('q'))
  • Общее значение по умолчанию – 0.

Пользовательское действие должно быть довольно легко изменить, если вы хотите другое поведение. Например, некоторые люди предпочитают, чтобы любой --quiet сбрасывал уровень на 0 или даже на -1. Для этого удалите nargs из add_argument -q и --quiet, а также жестко задайте value = 0 if option_string[0] == 'q'.

Правильные ошибки парсера хорошо печатаются, если использование неправильное:

./script -vvvvvv -v 4 -v 0 -v -vvv --verbose --quiet 2 -v qqvvqvav
usage: script [-h] [--verbose [level]]
              [-v [level]] [--quiet [level]] [-q [level]]
script: error: argument -v: Option string for -v/-q must contain only further 'v'/'q' letters
person cfi    schedule 28.08.2012
comment
Мне это очень нравится, но кажется, что «по умолчанию» отсутствуют, а по умолчанию = по умолчанию ['подробный']) не будет работать? Извините, если я что-то упустил. - person Iliah Borg; 16.08.2020

argparse поддерживает действие append, которое позволяет указать несколько аргументов. Проверьте http://docs.python.org/library/argparse.html, найдите "append".

person yan    schedule 20.05.2011

Ваш первый предложенный метод, скорее всего, запутает. Различные имена опций для разных уровней детализации или один флаг детализации, за которым может следовать числовой индикатор уровня детализации, с меньшей вероятностью запутают пользователя и обеспечат большую гибкость при назначении уровней детализации.

person Paddy3118    schedule 21.05.2011
comment
Этот метод не редкость. Например, SSH использует его. - person Charles Brunet; 21.05.2011

Я придумал альтернативу; хотя он точно не соответствует запросу OP, он удовлетворил мои требования, и я подумал, что им стоит поделиться.

Используйте взаимоисключающую группу либо для подсчета количества коротких опций, либо для хранения целочисленного значения длинной опции.

import argparse

parser = argparse.ArgumentParser()
verbosity_group = parser.add_mutually_exclusive_group()
verbosity_group.add_argument(
  '-v',
  action='count',
  dest='verbosity',
  help='Turn on verbose output. Use more to turn up the verbosity level'
)
verbosity_group.add_argument(
  '--verbose',
  action='store',
  type=int,
  metavar='N',
  dest='verbosity',
  help='Set verbosity level to `N`'
)
parser.set_defaults(
  verbosity=0
)
parser.parse_args()
parser.parse_args([])
# Namespace(verbosity=0)
parser.parse_args(['-v', '-vv'])
# Namespace(verbosity=3)
parser.parse_args(['--verbose=4'])
# Namespace(verbosity=4)
parser.parse_args(['--verbose'])
# error: argument --verbose: expected one argument

Как вы можете видеть, это позволяет вам «складывать» одиночные параметры char и позволяет вам использовать длинное имя параметра для явной установки значения. Недостатком является то, что вы не можете использовать опцию long в качестве переключателя (последний пример генерирует исключение).

person Rowshi    schedule 08.05.2020