首页 > 编程问答 >如何使 argparse 与枚举和默认值完美配合?

如何使 argparse 与枚举和默认值完美配合?

时间:2024-07-22 13:28:27浏览次数:18  
标签:python enums argparse

我有一个枚举:

from enum import auto, Enum


class MyEnum(Enum):
    ONE = auto()
    TWO = auto()
    THREE = auto()

,我想将它用作 argparse 的参数。更具体地说,我想创建一个接受枚举名称之一 ("one", "two", "three") 并且可能具有默认值的参数,并且相应的枚举成员存储在命名空间中。换句话说,我想做这样的事情:

from argparse import ArgumentParser
from enum import auto, Enum


class MyEnum(Enum):
    ONE = auto()
    TWO = auto()
    THREE = auto()


enum_map = {e.name.lower(): e for e in MyEnum}

parser = ArgumentParser()
parser.add_argument(
    "--enum",
    default="two",
    choices=tuple(enum_map.keys()),
    help="An enum value (Default: %(default)s)",
)
args = parser.parse_args()
args.enum = enum_map[args.enum]
print(args.enum)

这是一些样板代码,我想使用自定义操作消除它。我从 this SO 答案中得到灵感,并得到了以下示例:

from argparse import Action, ArgumentParser, FileType, Namespace
from collections.abc import Callable, Sequence
from enum import auto, Enum
from typing import Any


class EnumAction[T](Action):
    _enum: type[T]
    _enum_map: dict[str, T]

    def __init__(
        self,
        option_strings: Sequence[str],
        dest: str,
        nargs: int | str | None = None,
        const: Any = None,
        default: str | T = None,
        type: Callable[[str], T] | FileType | None = None,
        choices: Sequence[T] | None = None,
        required: bool = False,
        help: str | None = None,
        metavar: str | tuple[str, ...] | None = None,
    ) -> None:
        if type is None:
            raise ValueError("type must be assigned an Enum when using EnumAction")
        if not issubclass(type, Enum):
            raise TypeError("type must be an Enum when using EnumAction")
        if choices is not None:
            raise ValueError("Can't specify choices when using EnumAction")

        self._enum = type
        type = None
        self._enum_map = {e.name.lower(): e for e in self._enum}
        choices = tuple(self._enum_map.keys())
        super().__init__(
            option_strings,
            dest,
            nargs,
            const,
            default,
            type,
            choices,
            required,
            help,
            metavar,
        )

    def __call__(
        self,
        parser: ArgumentParser,
        namespace: Namespace,
        values: str | Sequence[Any] | None,
        option_string: str | None = None,
    ) -> None:
        setattr(namespace, self.dest, self._enum_map[values])


class MyEnum(Enum):
    ONE = auto()
    TWO = auto()
    THREE = auto()


parser = ArgumentParser()
parser.add_argument(
    "--enum",
    action=EnumAction,
    type=MyEnum,
    default="two",
    help="An enum value (Default: %(default)s)",
)
print(parser.parse_args())

它工作得很好,除了一个问题:如果我在没有 --enum 参数的情况下调用程序,我会得到默认值| ||,但它没有被我的操作处理,所以我得到字符串本身而不是枚举成员: "two" 最简单的解决方法是将默认值设置为

$ python demo.py --enum one
Namespace(enum=<MyEnum.ONE: 1>)

$ python demo.py
Namespace(enum='two')

而不是 default=MyEnum.TWO 或使我的操作转换如果是字符串,则将 "two" 参数放入相应的枚举成员中。但是,这会导致枚举成员显示在不太可读的帮助消息中(这就是我将选项设置为成员名称而不是实际成员的原因): default 如何获得枚举在传递参数和使用默认值时都在命名空间中添加成员,并在帮助消息中看到成员的名称而不是成员本身?

$ python demo.py -h
usage: demo.py [-h] [--enum {one,two,three}]

options:
  -h, --help            show this help message and exit
  --enum {one,two,three}
                        An enum value (Default: MyEnum.TWO)

How can I get an enum member in the namespace both when passing an argument and when using the default value, and see the member's name in the help message instead of the member itself?


可以通过在 __call__ 方法中处理默认值并使用 argparse metavar 参数来自定义帮助消息来实现此目的。

以下是更新后的代码:

from argparse import Action, ArgumentParser
from enum import Enum, auto


class EnumAction(Action):
    """
    Argparse action for handling Enums
    """

    def __init__(self, **kwargs):
        # Pop enum type from kwargs, since we need to handle it ourselves
        self._enum = kwargs.pop("type", None)
        if self._enum is None:
            raise ValueError("type must be assigned an Enum when using EnumAction")
        if not issubclass(self._enum, Enum):
            raise TypeError("type must be an Enum when using EnumAction")

        # Generate choices and enum map
        self._enum_map = {e.name.lower(): e for e in self._enum}
        kwargs["choices"] = tuple(self._enum_map.keys())

        # Use enum names for metavar display in help message
        kwargs.setdefault("metavar", '{' + ','.join(self._enum_map.keys()) + '}')

        super().__init__(**kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        if values is not None:
            # Convert the input value to the enum member
            value = self._enum_map.get(values.lower())
            if value is None:
                raise ValueError(
                    f"Invalid choice: '{values}' (choose from {', '.join(self._enum_map.keys())})"
                )
            setattr(namespace, self.dest, value)
        else:
            # Use the default if provided, otherwise set to None
            setattr(namespace, self.dest, getattr(self, "default", None))


class MyEnum(Enum):
    ONE = auto()
    TWO = auto()
    THREE = auto()


parser = ArgumentParser()
parser.add_argument(
    "--enum",
    action=EnumAction,
    type=MyEnum,
    default=MyEnum.TWO,  # Set default to enum member
    help="An enum value (Default: %(default)s)",
)
args = parser.parse_args()

print(args)
print(args.enum)

代码解释:

  1. 处理默认值:
    • 我们在 EnumAction __call__ 方法中检查 values 是否为 None 。如果是,则表示没有提供命令行参数,我们使用 getattr(self, "default", None) 获取默认值。
  2. 自定义帮助消息:
    • 我们在 EnumAction __init__ 方法中使用 kwargs.setdefault("metavar", ...) 来设置 metavar 参数。这将覆盖默认的元变量显示,并在帮助消息中使用枚举成员的名称。
  3. 使用枚举成员作为默认值:
    • add_argument 调用中,我们将 default 设置为 MyEnum.TWO ,这将确保默认情况下使用正确的枚举成员。

现在,当运行脚本时,无论是否提供 --enum 参数,都将获得预期的行为和帮助消息:

$ python demo.py --enum one
Namespace(enum=<MyEnum.ONE: 1>)
<MyEnum.ONE: 1>

$ python demo.py 
Namespace(enum=<MyEnum.TWO: 2>)
<MyEnum.TWO: 2>

$ python demo.py -h
usage: demo.py [-h] [--enum {one,two,three}]

options:
  -h, --help            show this help message and exit
  --enum {one,two,three}
                        An enum value (Default: MyEnum.TWO)

标签:python,enums,argparse
From: 78750965

相关文章

  • 正则表达式在python爬虫中常用的方法举例
    在爬虫中,正则表达式被广泛用于从网页中提取特定信息。以下是一些常用的正则表达式方法举例,以及它们在爬虫中的典型应用场景:1.提取URLimportreurl_pattern=r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'urls=re.findall(url_pattern,html_content)用于从网页中......
  • 使用 Python XlsxWriter 将 DatePicker 添加到 Excel 单元格中?
    我正在尝试使用PythonXlsxWriter生成的Excel创建输入表单。我想知道是否可以在Excel单元格中添加一个迷你日历(作为DatePicker)供用户输入日期?我偶然发现了Microsoft支持团队提供的本指南插入日期选择器:|||https://support.microsoft.com/en-us/office/......
  • [1037] Python operation of three keys shortcut (pynput)
    Theshortcutof win+shift+leftdoesnotworkwellin pyautogui,butitworkswellin pynput.MovingtheActiveWindowtoaDifferentMonitor: You’reright;PyAutoGUIdoesn’tdirectlysupportmovingwindowsacrossmonitorswiththeeleganceofaswan......
  • Python:定期检测断开故障的USB设备并重新初始化实例
    我有一个USB设备,有时会通过USB端口发送串行数据。问题是设备出现故障,有时会无缘无故地断开连接并再次连接到电脑。问题不大,但在这些情况下我需要重新初始化serial.Serial(port)实例,这有点烦人。该设备没有可以从我那里收到的任何命令,我可以验证它是否已连接。我可以......
  • 【校招+社招】华为OD机试 - 拼接URL(Java、JavaScript、Python、C、C++)
    鱼弦:公众号【红尘灯塔】,CSDN博客专家、内容合伙人、新星导师、全栈领域优质创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)算法概述URL拼接(URL拼接)是指将多个URL组件(方案、主机、端口、路径、查询参......
  • 使用 Google Colab 时,Python 包“datasets”从 virtualenv 目录“site-packages”中消
    我正在使用GoogleColab并尝试创建一个虚拟环境来工作。我的代码是:fromgoogle.colabimportdrivedrive.mount('/content/drive')!pipinstallvirtualenvmyenv_dir='/content/drive/MyDrive/virtual_env/'!virtualenv{myenv_dir}!chmod+x{myen......
  • Python 3 - openpyxl - 按名称迭代列
    使用openpyxl不按数字而是按列标题(ws第一行中的字符串值)迭代列的最简单方法是什么:如下所示:forcellinws.columns['revenue']:print(cell.value)不幸的是,openpyxl不直接支持像ws.columns['revenue']这样按列标题进行迭代。openpyxl......
  • Python selenium 网络抓取 recaptcha
    我想抓取一个网站,但在此之前有一个验证码,我什至使用api获取了数据,并且我还将其注入到网站中,因为网页没有提交按钮,我无法提交。流程是这样的,如果我解决同一网址中的验证码,隐藏的内容将被显示。但它并没有得到解决。我到处都找过了。我找不到解决方案。谁能帮我解决这个问题?......
  • Python 装饰器 详解+案例
    Python装饰器是一种特殊的函数,用于修改其他函数的功能。装饰器可以在不改变原函数代码的情况下,对函数进行增加、修改或者扩展功能。装饰器的语法形式是在函数定义前使用@符号,并在@后面加上装饰器的名称。装饰器函数接受被装饰函数作为参数,并返回一个修改后的函数。impo......
  • 如何在 vercel 部署中路由 python 和 typescript 无服务器函数
    我从一个带有Next.js和Typescript前端以及python后端的全栈应用程序开始。由于我们想在vercel上部署,因此我们将所有后端功能迁移到/api文件夹中的typescript函数中,可通过以下方式访问:fetch('api/**foldername**)问题是我有一个简单的pytorch模型,因此......