命令帮助(通过-h)其中`argparse`是范围检查输入端口号

时间:2016-06-07 13:21:52

标签: python python-3.x port argparse

我使用argparse来解析我的python3程序的输入。我最近被要求检查一些数字输入,看似好主意。 Argparse有一个可以做到这一点的工具。

数字输入是端口号,通常在0-65535范围内,所以我将我的解析命令行改为:

import argparse
cmd_parser = argparse.ArgumentParser()
cmd_parser = add_argument('-p', help='Port number to connect to', dest='cmd_port', default=1234, type=int, choices=range(0,65536))
cmd_parser.parse_args(['-h'])

然而,现在,当我请求帮助时,我充斥着来自argparse的所有可能值。例如

optional arguments:
    -h, --help            show this help message and exit
    -p {0,1,2,3,4,5,6,7,8,9,10,11,12,13 ...
    65478,65479,65480,65481,65482,65483,65484,65485,65486,65487,65488,65489,
    65490,65491,65492,65493,65494,65495,65496,65497,65498,65499,65500,65501,
    65502,65503,65504,65505,65506,65507,65508,65509,65510,65511,65512,65513,
    65514,65515,65516,65517,65518,65519,65520,65521,65522,65523,65524,65525,
    65526,65527,65528,65529,65530,65531,65532,65533,65534,65535}
                    Port number to connect to
...

列出该范围内的每个端口。有没有办法截断这个或让它实现它的范围(0-65535)或者它使用省略号或什么使它更漂亮?我唯一的选择是使用if语句明确检查我的输入吗?

我一直在谷歌搜索这个,但我很难找到人们使用argparse和指定选择的例子。我还检查了argparse的文档,但没有看到任何有用的东西。 https://docs.python.org/2/library/argparse.html

6 个答案:

答案 0 :(得分:3)

使用自定义操作...

import argparse

class PortAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if not 0 < values < 2**16:
            raise argparse.ArgumentError(self, "port numbers must be between 0 and 2**16")
        setattr(namespace, self.dest, values)

cmd_parser = argparse.ArgumentParser()
cmd_parser.add_argument('-p',
                        help='Port number to connect to',
                        dest='cmd_port',
                        default=1234,
                        type=int,
                        action=PortAction,
                        metavar="{0..65535}")

无效的端口号将根据引发的ArgumentError显示错误消息。如果输入值65536,将打印以下行:

error: argument -p: port numbers must be between 0 and 2**16

将根据显示的metavar打印使用和帮助消息

答案 1 :(得分:2)

int中使用add_argument作为type,并在允许的范围内手动验证它。或者,使用您自己的类型,其中包含一个为您进行检查的构造函数,以及一个用于隐式转换的__int__方法:

class portnumber:
    def __init__(self, string):
        self._val = int(string)
        if (not self._val > 0) or (not self.val < 2**16):
            raise argparse.ArgumentTypeError("port numbers must be integers between 0 and 2**16")
    def __int__(self):
        return self._val

...

parser.add_argument("-p",type=portnumber)

答案 2 :(得分:2)

提供明确的metavar参数,而不是让argparse为您生成一个。

cmd_parser.add_argument('-p',
                        help='Port number to connect to',
                        dest='cmd_port',
                        default=1234,
                        type=int,
                        choices=range(0,65536),
                        metavar="{0..65535}")

答案 3 :(得分:1)

有关大型choices列表格式的Python错误/问题。目前帮助中的choices格式为

    def _metavar_formatter: ...
        choice_strs = [str(choice) for choice in action.choices]
        result = '{%s}' % ','.join(choice_strs)

以及错误:

def _check_value(self, action, value):
    # converted value must be one of the choices (if specified)
    if action.choices is not None and value not in action.choices:
        args = {'value': value,
                'choices': ', '.join(map(repr, action.choices))}
        msg = _('invalid choice: %(value)r (choose from %(choices)s)')
        raise ArgumentError(action, msg % args)

所以它希望choices是一个可迭代的,但不会压缩列表或使其漂亮。请注意,唯一的测试是value not in action.choiceschoices是一个非常简单的功能。

我在之前的SO问题中提到了这个问题:http://bugs.python.org/issue16468。涉及建议的补丁,因此不要期待任何修复。

我建议您使用自己的type测试,而不是choices。或者在解析后进行自己的范围测试。

def myrange(astring):
   anint = int(astring)
   if anint in range(0,1000):
        return anint
   else:
        raise ValueError()
        # or for a custom error message
        # raise argparse.ArgumentTypeError('valid range is ...')

parser.add_argument('value',type=myrange,metavar='INT',help='...')

另一个旧的(2012)SO问题,解决范围选择。答案建议使用帮助格式化程序修复程序和自定义类型

Python's argparse choices constrained printing

Python argparse choices from an infinite set

============================

出于好奇,我定义了一个自定义range类。它的行为类似于step测试的常规范围(没有in),但在用作迭代器时会返回自定义值。

class Range(object):
    def __init__(self, start, stop, n=3):
        self.start = start
        self.stop = stop
        self.n = n
    def __contains__(self, key):
        return self.start<=key<self.stop   
    def __iter__(self):
        if self.stop<(self.start+(self.n*3)):
            for i in range(self.start, self.stop):
                yield i
        else:
            for i in range(self.start, self.start+self.n):
                yield i
            yield '...'
            for i in range(self.stop-self.n, self.stop):
                yield i

与参数一起使用时

parser.add_argument("-p",type=int, choices=Range(2,10,2))

它产生

1455:~/mypy$ python stack37680645.py -p 3
Namespace(p=3)

1458:~/mypy$ python stack37680645.py -h
usage: stack37680645.py [-h] [-p {2,3,...,8,9}]

optional arguments:
  -h, --help        show this help message and exit
  -p {2,3,...,8,9}

错误消息并不是我想要的,而是关闭

1458:~/mypy$ python stack37680645.py -p 30
usage: stack37680645.py [-h] [-p {2,3,...,8,9}]
stack37680645.py: error: argument -p: invalid choice: 30 (choose from 2, 3, '...', 8, 9)

如果choices格式允许action.choices对象创建自己的strrepr字符串,那会更好。

实际上iter可能就像(只有一个字符串)一样简单:

def __iter__(self):
    yield 'a custom list'

另一种选择是使用metavar来控制使用/帮助显示,并使用此__iter__来控制错误显示。

使用metavar时要注意的一件事。当有特殊字符(例如空格)时,usage格式化程序不起作用,&#39;()&#39;和&#39; []&#39;在metavar中,特别是当使用线延伸到2行或更多行时。这是一个已知的错误/问题。

答案 4 :(得分:0)

Monkey修补print_help以获得所需的输出

def my_help(): print "0-65535 range"
cmd_parser = argparse.ArgumentParser()
cmd_parser.add_argument('-p', help='Port number to connect to',   dest='cmd_port', default=1234, type=int, choices=range(0,65536))
cmd_parser.print_help = my_help
cmd_parser.parse_args()

答案 5 :(得分:0)

我使用的答案是受@hpaulj的答案启发的。但是,我同意问题确实出在argparse的脚下。当我的计算机挂起尝试分配千兆字节的空间只是为了输出帮助文本时,我发现了这个SO查询。

我与hpaulj的“ Range”类有关的问题是,当使用较大的上限时,该消息仍会推出非常大的数字。 以下类默认情况下使用无限上限。

class ArgRange(object):
    from decimal import Decimal
    huge = Decimal('+infinity')
    huge_str = '{:.4E}'.format(huge)

    def __init__(self, start, stop=huge, n=3):
        self.start = start
        self.stop = stop
        self.n = n

    def __contains__(self, key):
        return self.start <= key < self.stop

    def __iter__(self):
        if self.stop < self.start+(self.n*3):
            for i in range(self.start, self.stop):
                yield i
        else:
            for i in range(self.start, self.start+self.n):
                yield I
            if self.stop is self.huge:
                yield '...' + huge_str
            else:
                yield '...'
                for i in range(self.stop - self.n, self.stop):
                    yield i

bounds = ArgRange(2)
balance = ArgRange(0, 1000)
parser = argparse.ArgumentParser(description="Do something fun")
parser.add_argument("width", type=int, choices=bounds,  default=9)
parser.add_argument("height", type=int, choices=balance, default=200)

值不正确,错误是:

argument width: invalid choice: 1 (choose from 2, 3, 4, '...infinity')

argument height: invalid choice: 2000 (choose from 0, 1, 2, '...', 997, 998, 999)

用法如下:

usage: test.py 
           {2,3,4,...infinity} {0,1,2,...,997,998,999}