python中,如何优雅的解析和管理命令行参数

发布时间 2023-09-22 13:47:35作者: 豆蛙

背景

我们在编写python程序时,程序中经常会提供多种功能或者模式,在实际使用时根据不同的参数使用不同的功能。那么如何获取命令行传入进来的参数呢?

一般方法

一般情况下,我们会使用 sys 模块,如?

import sys

# 打印 sys 模块获取到的命令行参数
print(sys.argv)

或者,我们会使用 getopt 模块,如?

import getopt

opts,args=getopt.getopt(sys.argv[1:],"i:ho:",["help","input=","output="])
# 打印选项列表
print(opts)
# 打印参数值列表
print(args)

# 解析参数对应的值
for opts,arg in opts:
	print(opts)
	if opts=="optName":
		print("optName is value is:", arg)

再或者,我们使用 argparse 模块,如?

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-a', '--arg1', help='argument 1')
parser.add_argument('-b', '--arg2', help='argument 2')
parser.add_argument('-c', '--arg3', help='argument 3')

args = parser.parse_args()

print(args.arg1)
print(args.arg2)
print(args.arg3)

但,以?这些,都不优雅,如果我们需要在不同的函数或者模块中传递使用命令行进来的参数,那这些零散的参数处理代码,将会带来不小的麻烦。我们需要通过一个专门的类来封装命令行的参数。

引入 DebugInfo 模块

pip install DebugInfo

定义一个命令行参数类,

定义一个入参类,继承自 入参基类,来专门负责解析和管理我们的传入参数,示例见?:

# -*- coding:UTF-8 -*-

# region 引入必要依赖
from DebugInfo.DebugInfo import *

# endregion

class 入参类(入参基类):
    def __init__(self):
        # 初始化基类
        super().__init__(接口说明='这个脚本是用来演示如何使用【入参基类】来管理传入参数的')


if __name__ == '__main__':
    # 打印一下 入参基类 的说明文档
    print(入参基类.__doc__)

?上面的代码初步建立了入参类,继承自入参基类,我们通过打印入参基类doc 信息,可以查阅到相关的使用说明,如?
DebugInfo 入参基类 __doc__信息

如?,我们通过 doc 的说明信息得知,这个入参基类其实是在 argparse 的基础上做了二次封装,然后提供了一些易用的接口。?

?下面的代码为入参类添加了我们需要接收的参数,并实例化了入参类,并解析了命令行参数:

# -*- coding:UTF-8 -*-

# region 引入必要依赖
from DebugInfo.DebugInfo import *

# endregion

class 入参类(入参基类):
    def __init__(self):
        # 初始化基类
        super().__init__(接口说明='这个脚本是用来演示如何使用【入参基类】来管理传入参数的')

        # 添加需要接收的参数
        self._添加参数('a', int, '这是 a 参数, 请输入一个数字', 0)
        self._添加参数('b', int, '这是 b 参数, 请输入一个数字', 0)
        self._添加参数('运算', ['和', '差'], '请输入运算形式', '和')


if __name__ == '__main__':
    # 实例化入参类
    入参 = 入参类()

    # 解析命令行参数
    入参.解析入参()

    # 打印获取到的参数
    print(入参.get('a'))
    print(入参.get('b'))
    print(入参.get('运算'))

?上面的代码运行效果如?:
DebugInfo 入参基类 解析命令行参数效果

当我们在命令行中使用 -h 参数时,还会有相应的参数帮助提示,如?
DebugInfo 入参基类 命令行参数提示

当我们输入的参数不正确时,还有相应的错误提示,如?
DebugInfo 入参基类 命令行参数错误提示

这么多功能,相对于我们输入的代码量来说,是不是物超所值?优雅至极?

百尺杆头,更进一步

上面的代码中,我们通过 入参.get('a') 这样的方式获取了参数 a 的值.这不够优雅.
如?的代码中,我们通过在 入参类 中定义property的方法,将每一个参数成员定义对应的getter接口,这样就可以通过 入参.a 这种方式获取和使用参数值了.同时也获得了 IDE 的成员提示和代码补全支持.

# -*- coding:UTF-8 -*-

# region 引入必要依赖
from DebugInfo.DebugInfo import *

# endregion

class 入参类(入参基类):
    def __init__(self):
        # 初始化基类
        super().__init__(接口说明='这个脚本是用来演示如何使用【入参基类】来管理传入参数的')

        # 添加需要接收的参数
        self._添加参数('a', int, '这是 a 参数, 请输入一个数字', 0)
        self._添加参数('b', int, '这是 b 参数, 请输入一个数字', 0)
        self._添加参数('运算', ['和', '差'], '请输入运算形式', '和')

    # region 访问器
    @property
    def a(self) -> int:
        if 'a' in self._参数字典:
            return self._参数字典['a'].值
        else:
            return 0

    @a.setter
    def a(self, 值: int):
        if 'a' in self._参数字典:
            if type(值) in [int, float]:
                self._参数字典['a'].值 = int(值)

    @property
    def b(self) -> int:
        if 'b' in self._参数字典:
            return self._参数字典['b'].值
        else:
            return 0

    @b.setter
    def b(self, 值: int):
        if 'b' in self._参数字典:
            if type(值) in [int, float]:
                self._参数字典['b'].值 = int(值)

    @property
    def 运算(self) -> str:
        if '运算' in self._参数字典:
            return self._参数字典['运算'].值
        else:
            return ''

    @运算.setter
    def 运算(self, 值: str):
        if '运算' in self._参数字典:
            self._参数字典['运算'].值 = str(值)
    # endregion


if __name__ == '__main__':
    # 实例化入参类
    入参 = 入参类()

    # 解析命令行参数
    入参.解析入参()

    # 打印获取到的参数
    print(入参.a)
    print(入参.b)
    print(入参.运算)

如此优雅的命令行参数解析和管理方式,我相信你从来没有见过,独此一份了.

效率支持

上面的代码中,我们看到我们为每一个参数写一个 property 的接口,好麻烦啊.优雅个毛线.
no!no!no! 你能想到的需求,作者当然要支持上了.

?下面的代码中, 我们通过 入参.转换为属性范式() 将每一个命令行参数自动生成其对应的访问器接口,并且自动送到您的粘贴板里,您唯一需要做的就是 ctrl-V, 惊不惊喜? 意不意外?

# -*- coding:UTF-8 -*-

# region 引入必要依赖
from DebugInfo.DebugInfo import *

# endregion

class 入参类(入参基类):
    def __init__(self):
        # 初始化基类
        super().__init__(接口说明='这个脚本是用来演示如何使用【入参基类】来管理传入参数的')

        # 添加需要接收的参数
        self._添加参数('a', int, '这是 a 参数, 请输入一个数字', 0)
        self._添加参数('b', int, '这是 b 参数, 请输入一个数字', 0)
        self._添加参数('运算', ['和', '差'], '请输入运算形式', '和')


if __name__ == '__main__':
    # 实例化入参类
    入参 = 入参类()

    # 对每一个命令行参数,生成其对应的 访问器接口
    入参.转换为属性范式()

?上面的代码中, 入参.转换为属性范式() 的效果如?:
DebugInfo 入参基类 命令行参数访问器接口自动生成效果

共生成了32行代码,每个参数的setter, getter属性都给你准备好了, ctrl+V 是您唯一需要做的事情了.
不让告诉我你不知道在哪里 ctrl+V ?

如果你不希望命令行参数被setter, 你可以通过函数参数控制生成,如?

if __name__ == '__main__':
    # 实例化入参类
    入参 = 入参类()

    # 对每一个命令行参数,生成其对应的 访问器接口
    入参.转换为属性范式(setter=False)

小结

以上所分享的命令行参数解析+管理的方式,提供自 DebugInfo 模块内的 入参基类, 虽然是基于 argparse 的一个二次封装,但相对于直接使用 argparse, 确实方便和清晰不少.