我使用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
答案 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.choices
。 choices
是一个非常简单的功能。
我在之前的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
对象创建自己的str
或repr
字符串,那会更好。
实际上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}