【问题标题】:Declaring a positional argument inside a group of optional arguments in Python在 Python 中的一组可选参数中声明位置参数
【发布时间】:2015-01-28 00:17:12
【问题描述】:

我想在 Python 中使用argparse 来声明参数如下:

./get_efms_by_ids [-h] [-v] [inputfile [1 3 4 9] [-c 11..18] [20 25 40]]

在这种情况下我想做的是:
如果使用inputfile,则可以采用两种类型的可选参数:1 3 4 9c 11..18 或两者。如果我不输入inputfile,则必须缺少可选参数。

例如: 我可以给你看一些命令行用法的例子:

./get_efms_by_ids Vacf.txt // default: get 1 or 10 first lines in Vacf.txt
./get_efms_by_ids Vacf.txt 1 3 4 9 // get the lines that indexes: 1 3 4 9 in Vacf.txt
./get_efms_by_ids Vacf.txt c 11..18 22 25 29 // get the lines that indexes are from 11 to 18, then the lines 22, 25, 29
./get_efms_by_ids c 11.. 18 // shows a readable error message
./get_efms_by_ids 1 3 4 9 // shows a readable error message

可以使用args='?'args='*',如下例所示:

parser = argparse.ArgumentParser(description='Selecting some Elementary Flux Modes by indexes.',version='1.0')
parser.add_argument('efm_matrix_file', type=file, help='give the name of the efms matrix file')
parser.add_argument('ids', nargs='?', help='give the indexes of the chosen efms')
parser.add_argument('-i','--indexes',nargs='*', help='give the begin and start indexes of the chosen efms')

但结果并不符合本文开头提出的目的。

任何帮助将不胜感激。

【问题讨论】:

  • 不清楚你想要什么。我的猜测是 inputfile 是可选的,但如果它在那里,你不想要以下任何一个、一些或全部,按顺序:1、3、4、9 之一; -c 一个参数在 11 和 18 之间;和 20、25、40 之一?
  • @chepner: inputfile 是可选的。实际上,我想知道 inputfile 没有使用,其余可选参数将不存在。换句话说,它们依赖于inputfile。我使用-c 11..18 来获取索引范围为 11 到 18 的所有行。如果使用 1、3、4、9 等,我想获取各个元素。
  • 参数组不影响解析;他们只是在帮助信息中组织论据。
  • type=file 是错误的。 type 采用将字符串转换为其他内容的函数。我建议接受一个文件名(字符串)并稍后打开它。

标签: python arguments argparse


【解决方案1】:

首先,我会放弃-c 选项。您不需要同时使用 -c.. 来指示值的范围。这将简化您对类似

的调用
./get_efms_by_ids [-h] [-v] [inputfile [index ...]]

其中每个index 可以是单个整数或lower..upper 指定的范围。

参数解析器可以是一个简单的 as

def index_type(s):
    try:
        return int(s)
    except ValueError:
        try:
            return map(int, s.split(".."))
        except:
            raise ArgumentTypeError("Invalid index: %s" % (s,))

p = ArgumentParser()
p.add_argument("-h")
p.add_argument("-v")
p.add_argument("inputfile", nargs="?")
p.add_argument("indices", nargs="*", type=index_type)
args = p.parse_args()

if not (args.inputfile is None or os.path.exists(args.inputfile)):
    sys.exit("Invalid file name: %s" % (args.inputfile,))

您必须检查第一个位置参数(如果有)是否是有效文件解析之后,因为任何任意字符串可能都是有效文件名字。

index_type 函数只是在解析过程中如何转换每个索引(无论是整数还是范围)的一个示例。

【讨论】:

  • 如果在没有inputfile 的情况下使用./get_efms_by_ids 11 14 16,我将无法控制错误。它也不会跳转到ArgumentError()
  • 我用简单的sys.exit 替换了ArgumentError,因为ArgumentError 的真正目的是从parse_args 引发(它需要一个实际的参数对象作为它的参数之一)。
  • 我收到错误消息:File "/usr/local/bin/get_efms_by_ids.py", line 52, in main if not(args.inputfile is None or os.path.isfile(args.inputfile)): File "/usr/lib/python2.7/genericpath.py", line 29, in isfile st = os.stat(path) TypeError: coercing to Unicode: need string or buffer, file found
  • 我看不出你是如何从我发布的代码中得到这个错误的。
  • 我对评论表示歉意。我写错了语法。
【解决方案2】:

我采用与 chepner 不同的方法,但借鉴了 chepner 的一些想法:放弃 -c 选项并使用修改后的 index_type()

代码

#!/usr/bin/env python
import argparse
from itertools import chain

def index_type(s):
    try:
        return [int(s)]
    except ValueError:
        try:
            start, stop = map(int, s.split('..'))
            return range(start, stop + 1)
        except:
            raise argparse.ArgumentTypeError("Invalid index: %s" % (s,))

def get_options():
    parser = argparse.ArgumentParser()
    parser.add_argument('-v')
    parser.set_defaults(fileinput=None)

    options, remaining = parser.parse_known_args()
    if remaining:
        parser = argparse.ArgumentParser()
        parser.add_argument('fileinput', type=argparse.FileType())
        parser.add_argument('selected_lines', nargs='*', type=index_type)
        parser.parse_args(remaining, namespace=options)

        # Convert a nested list into a set of line numbers
        options.selected_lines = set(chain.from_iterable(options.selected_lines))

        # If the command line does not specify the line numbers, assume a default
        if not options.selected_lines:
            options.selected_lines = set(index_type('1..10'))

    return options

if __name__ == '__main__':
    options = get_options()

    # If the command line contains a file name, loop through the file and process only the lines
    # requested
    if options.fileinput is not None:
        for line_number, line in enumerate(options.fileinput, 1):
            if line_number in options.selected_lines:
                line = line.rstrip()
                print '{:>4} {}'.format(line_number, line)

讨论

  • argparse 模块允许可选参数,但 fileinput 不能是可选参数,因为它是一个位置参数——这就是 argparse 的操作方式
  • 为了绕过这个限制,我对命令行进行了两次解析:第一次是获取-v 标志。第一次解析,我使用parse_known_args()方法,它忽略了那些它不理解的参数。
  • 对于第二次解析,我处理剩余参数,假设第一个参数是文件名,后跟一系列行号
  • 解析行号很棘手。最终目标是将"11..18 1 3 4 9" 之类的东西转换为[1, 3, 4, 9, 11, 12, 13, 14, 15, 16, 17, 18]
  • 使用修改后的index_type()(感谢chepner),我能够将命令行从"11..18 1 3 4 9"解析为[11, 12, 13, 14, 15, 16, 17, 18], [1], [3], [4], [9]]
  • 下一步是将此嵌套列表转换为一组行号以便于查找
  • 作为奖励,如果命令行没有指定任何行号,我假设1..10
  • get_options 返回后,options.fileinput 将是 None 或文件句柄——无需打开文件即可读取。 options.selected_lines 将是一组可供选择的行号
  • 最后的任务就是过线,如果被选中,就处理。就我而言,我只是打印出来

【讨论】:

  • 我也打算在我的回答中发布类似的内容,但为了简单起见,我决定专注于一种方法。 parse_known_args 当然是一个不错的选择。
猜你喜欢
  • 2023-04-02
  • 2020-09-01
  • 2018-02-18
  • 2017-12-01
  • 1970-01-01
  • 2014-06-16
  • 2014-12-29
  • 2012-12-06
  • 1970-01-01
相关资源
最近更新 更多