返回

Python argparse:优雅处理交替位置参数

python

argparse 处理交替位置参数

argparse 是 Python 中用于处理命令行参数的强大工具,但它在处理特定类型的交替位置参数时会遇到挑战。这类问题常见于需要成对出现或以固定组大小出现的位置参数的情况。举例来说,我们可能期望命令行接收一个选项,然后跟随任意数量的 “文件 - 目标位置” 配对参数。这种模式很难直接使用 argparse 内置的功能来实现。

问题分析

问题的核心在于 argparse 默认以列表或字符串的方式收集位置参数,而不是按照特定规则或分组来处理。如果尝试简单声明多个位置参数,argparse 不会自动将它们视为成对出现。这会导致参数解析器无法正确判断参数的意图,以及参数输入的不正确模式。需要额外的逻辑来处理这种成组出现的位置参数。

解决方案:自定义操作

为了实现成对或分组的位置参数解析,需要自定义 argparseAction。通过继承 argparse.Action 类并重写其 __call__ 方法,可以实现自定义的行为。这个方法会在每个匹配到的参数时被调用。下面展示了两种策略:

策略一:成对处理(大小为2的组)

首先,我们创建一个继承 argparse.Action 的新类,用于收集文件和目标位置的配对信息。这个自定义的action在调用时,将依次接受文件路径和目标路径。将这些信息以列表元组的形式存储在指定位置。它还会进行检查,确保配对的参数存在且个数正确。

import argparse

class PairsAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        if nargs != argparse.REMAINDER:
            raise ValueError("nargs must be argparse.REMAINDER")
        super().__init__(option_strings, dest, nargs=nargs, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        pairs = []
        if len(values) % 2 != 0:
          raise argparse.ArgumentError(self, "需要成对的文件和目标参数")
        for i in range(0, len(values), 2):
            pairs.append((values[i], values[i+1]))
        setattr(namespace, self.dest, pairs)

# 使用方法
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--option', type=str, help="可选的选项")
parser.add_argument('files', action=PairsAction, nargs=argparse.REMAINDER, help="文件和目标路径列表")

# 解析
args = parser.parse_args()

# 输出结果,便于调试
print(args)

使用方法示例:

python your_script.py -o some_option file1 dest1 file2 dest2 file3 dest3
python your_script.py  file1 dest1 file2 dest2

在上述示例中,PairsAction 将命令行参数中的位置参数以 (file, dest) 的元组列表形式存储在 args.files 中。

策略二:通用分组处理 (可配置组大小)

这个策略通过一个参数接受期望的分组大小。 GroupsAction 的逻辑更通用一些。通过 nargs_size 设定每个参数组大小,每次调用 __call__ 时,都将以该大小读取参数,并将他们组成为一个元组,最终将所有分组添加到命名空间中指定的参数名称上。 同样的,他也会检测余下的参数个数,保证参数可以分组。

import argparse

class GroupsAction(argparse.Action):
  def __init__(self, option_strings, dest, nargs=None, nargs_size=None, **kwargs):
    if nargs != argparse.REMAINDER:
        raise ValueError("nargs must be argparse.REMAINDER")
    if nargs_size is None or not isinstance(nargs_size,int):
      raise ValueError("nargs_size should be a int.")

    self.nargs_size = nargs_size
    super().__init__(option_strings, dest, nargs=nargs, **kwargs)


  def __call__(self, parser, namespace, values, option_string=None):
    if len(values) % self.nargs_size != 0:
      raise argparse.ArgumentError(self, f"参数需要按照 {self.nargs_size} 分组")

    groups = []
    for i in range(0, len(values), self.nargs_size):
      groups.append(tuple(values[i:i + self.nargs_size]))
    setattr(namespace, self.dest, groups)

# 使用方法
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--option', type=str, help="可选的选项")
parser.add_argument('data', action=GroupsAction, nargs=argparse.REMAINDER, nargs_size=3, help="每组包含三个数据的列表")

# 解析
args = parser.parse_args()

# 输出结果,便于调试
print(args)

使用示例

python your_script.py -o option a b c d e f g h i

在以上示例中,GroupsAction 可以在初始化的时候配置 nargs_size=3,从而将 data 以 3 个元素的元组进行分组,添加到命名空间。如果用户输入的参数无法分组则抛出异常。

额外建议

  1. 详细错误消息 : 确保自定义操作中的错误消息能够准确指示参数格式错误的原因。这对提升用户体验至关重要。
  2. 文档完善 : 对于使用自定义 Action 的参数解析器,应提供详细的命令行帮助文档,明确说明期望的参数模式和使用方法。
  3. 单元测试 : 对自定义 Action 的行为进行单元测试是很有必要的,这可以帮助防止回归问题,确保在修改后仍能正常工作。
  4. 安全校验 : 输入参数的安全校验。应该对接受的路径等参数进行基本的校验,例如,路径是否符合操作系统规范等。防止注入或者其它意外情况发生。
  5. 参数限制 : 可以给一些 nargs_size 设定上下限。

通过以上两种策略,可以较为灵活的处理不同类型的参数分组问题。可以依据具体的需要选取合适的方法。