美文网首页
python3 编写命令行工具

python3 编写命令行工具

作者: mudssky | 来源:发表于2019-06-21 02:49 被阅读0次

    命令行程序对比GUI程序有很多优点:编写简单,参数形式统一,便于自动化,使用python编写命令行实现单一功能,然后用shell调用比较方便。
    此外命令行解析,用来调试程序也比较方便,不用到源码里改程序的参数了,用命令行指定就比较灵活。
    很多功能简单的程序,不涉及复杂的交互,用命令行比较方便,更好用。

    01.注释运行环境

    如果在linux系统上运行,通过注释运行环境,然后我们使用chmod a+x赋予执行权限,就可以直接运行文件不用指明解释器了,比较方便。

    #!/usr/bin/env python
    
    

    如果在windows系统中这个注释是没有效果的。windows系统通常是根据文件后缀选择打开的工具。

    02.命令行解析工具argparse的使用

    argparse是python标准库自带的命令行解析工具,功能比较强大。因为标准库自带,所以一般我们用这个写命令行就行了,比较方便。

    我们先来编写一个最简单的程序:

    %%writefile 'arghelp.py'
    import argparse
    parser = argparse.ArgumentParser()
    parser.parse_args()
    
    %%cmd
    python arghelp.py -h
    
    Microsoft Windows [版本 10.0.17763.557]
    (c) 2018 Microsoft Corporation。保留所有权利。
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>python arghelp.py -h
    usage: arghelp.py [-h]
    
    optional arguments:
      -h, --help  show this help message and exit
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>
    

    我们在cmd中执行这个程序,发现可以打印出唯一一条帮助文档。
    --help -h是默认会添加的选项,不需要定制

    001. 位置参数

    下面我们写一个简单的计算乘幂的程序来演示,位置参数的添加。

    %%writefile 'calpower.py'
    import argparse
    parser = argparse.ArgumentParser()
    # 加进去的字符串会被当成选项,注意类型直接写,不用字符串,这样子不带连接符的就是位置参数,是必须的。
    # 如果带连接符就是可选参数
    parser.add_argument('base',help='指数运算的底',type=int)
    parser.add_argument('power',help='指数运算的幂',type=int)
    args = parser.parse_args()
    print ('{}th power of {} is {}'.format(args.power,args.base,args.base**args.power))
    
    %%cmd
    python calpower.py 2 3
    
    Microsoft Windows [版本 10.0.17763.557]
    (c) 2018 Microsoft Corporation。保留所有权利。
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>python calpower.py 2 3
    3th power of 2 is 8
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>
    

    002.可选参数

    带连接符的参数就会被当做可选参数
    下面我们添加一个常见的功能,就是输出通常有简单模式,复杂模式两种

    %%writefile 'test/calpower.py'
    import argparse
    parser = argparse.ArgumentParser()
    # 加进去的字符串会被当成选项,注意类型直接写,不用字符串,这样子不带连接符的就是位置参数,是必须的。
    # 如果带连接符就是可选参数
    parser.add_argument('base',help='指数运算的底',type=int)
    parser.add_argument('power',help='指数运算的幂',type=int)
    # 可以直接添加简短复杂两种,总之加进去的字符串会被当成选项
    # 这里是个开关选项只有true和flase两个值,所以我们默认设为true
    parser.add_argument('-v','--verbose', help="increase output verbosity", action="store_true")
    args = parser.parse_args()
    if args.verbose:
        print('verbose on')
        print ('{}th power of {} is {}'.format(args.power,args.base,args.base**args.power))
    else:
        print(args.base**args.power)
    
    %%cmd
    python test/calpower.py  2 3 
    
    Microsoft Windows [版本 10.0.17763.557]
    (c) 2018 Microsoft Corporation。保留所有权利。
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>python test/calpower.py  2 3 
    8
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>
    
    %%cmd
    python test/calpower.py -v 2 3 
    
    Microsoft Windows [版本 10.0.17763.557]
    (c) 2018 Microsoft Corporation。保留所有权利。
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>python test/calpower.py -v 2 3 
    verbose on
    3th power of 2 is 8
    
    D:\code\Projects\jupyterNotes\python3\python3笔记>
    

    也就是说action='store_true'意思是,当有这个选项时就把值设置为true

    add_argument还有一些参数,比如说 choices=[0, 1, 2],这样就可以在指定范围里选
    count参数,这样就可以用来指定输出的级别
    default可以指定默认值

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("square", type=int,
                        help="display the square of a given number")
    parser.add_argument("-v", "--verbosity", action="count",
                        help="increase output verbosity",default=0)
    args = parser.parse_args()
    answer = args.square**2
    if args.verbosity == 2:
        print("the square of {} equals {}".format(args.square, answer))
    elif args.verbosity == 1:
        print("{}^2 == {}".format(args.square, answer))
    else:
        print(answer)
    

    003.互斥参数

    使用add_mutually_exclusive_group()这个方法可以添加互斥参数

    import argparse
    
    parser = argparse.ArgumentParser()
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-v", "--verbose", action="store_true")
    group.add_argument("-q", "--quiet", action="store_true")
    parser.add_argument("x", type=int, help="the base")
    parser.add_argument("y", type=int, help="the exponent")
    args = parser.parse_args()
    answer = args.x**args.y
    
    if args.quiet:
        print(answer)
    elif args.verbose:
        print("{} to the power {} equals {}".format(args.x, args.y, answer))
    else:
        print("{}^{} == {}".format(args.x, args.y, answer))
    

    004.添加工具的描述

    只需要在创建parser对象时给出description参数即可

    import argparse
    
    parser = argparse.ArgumentParser(description="calculate X to the power of Y")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-v", "--verbose", action="store_true")
    group.add_argument("-q", "--quiet", action="store_true")
    parser.add_argument("x", type=int, help="the base")
    parser.add_argument("y", type=int, help="the exponent")
    args = parser.parse_args()
    answer = args.x**args.y
    
    if args.quiet:
        print(answer)
    elif args.verbose:
        print("{} to the power {} equals {}".format(args.x, args.y, answer))
    else:
        print("{}^{} == {}".format(args.x, args.y, answer))
    

    005.详细参数说明

    parser = argparse.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=argparse.HelpFormatter, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True)

    创建命令行解析对象

    其中的参数:

    • prog - 程序的名字(默认:sys.argv[0])

    • usage - 描述程序用法的字符串(默认:从解析器的参数生成)

    • description - 参数帮助信息之前的文本(默认:空)

    • epilog - 参数帮助信息之后的文本(默认:空)

    • parents - ArgumentParser 对象的一个列表,这些对象的参数应该包括进去,像有时候需要解析非常复杂的关键字参数,比如像git那样的,

    • formatter_class - 定制化帮助信息的类

    • prefix_chars - 可选参数的前缀字符集(默认:‘-‘)

    • fromfile_prefix_chars - 额外的参数应该读取的文件的前缀字符集(默认:None)

    • argument_default - 参数的全局默认值(默认:None)

    • conflict_handler - 解决冲突的可选参数的策略(通常没有必要)

    • add_help - 给解析器添加-h/–help 选项(默认:True)

    parser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

    增加命令行参数,方法的参数说明如下:

    • name or flags 命令行参数名或者选项,如上面的address或者-p,--port.其中命令行参数如果没给定,且没有设置defualt,则出错。但是如果是选项的话,则设置为None,add_argument() 方法必须知道期望的是可选参数,比如-f 或者--foo,还是位置参数,比如一个文件列表。传递给add_argument() 的第一个参数因此必须是一个标记序列或者一个简单的参数名字。例如,一个可选的参数可以像这样创建:

    • action action 关键字参数指出应该如何处理命令行参数。支持的动作有:

      • 'store' - 只是保存参数的值。这是默认的动作

      • 'store_const' - 保存由const关键字参数指出的值。(注意const关键字参数默认是几乎没有帮助的None。)'store_const'动作最常用于指定某种标记的可选参数

      • 'store_true'和'store_false' - 它们是'store_const' 的特殊情形,分别用于保存值True和False。另外,它们分别会创建默认值False 和True。

      • 'append' - 保存一个列表,并将每个参数值附加在列表的后面。这对于允许指定多次的选项很有帮助。示例用法:

      • 'append_const' - 保存一个列表,并将const关键字参数指出的值附加在列表的后面。(注意const关键字参数默认是None。)'append_const' 动作在多个参数需要保存常量到相同的列表时特别有用。例如:

      • 'count' - 计算关键字参数出现的次数。例如,这可用于增加详细的级别:

      • 'help' - 打印当前解析器中所有选项的完整的帮助信息然后退出。默认情况下,help动作会自动添加到解析器中。参见ArgumentParser以得到如何生成输出信息。

      • 'version' - 它期待version=参数出现在add_argument()调用中,在调用时打印出版本信息并退出:

    • nargs 命令行参数的个数,一般使用通配符表示,其中,'?'表示只用一个,'*'表示0到多个,'+'表示至少一个

    • default 默认值
      type 参数的类型,默认是字符串string类型,还有float、int,file等类型

    • choices 可以看做是default的扩展,参数的值必须在choices的范围内

    • required 一般情况下,argparse模块假定-f和--bar标记表示可选参数,它们在命令行中可以省略。如果要使得选项是必需的,可以指定True作为required=关键字参数的值给add_argument()

    • help 和ArgumentParser方法中的参数作用相似,出现的场合也一致

    006.子解析

    像git这样复杂的命令行工具,会有add push pull等分支,需要用到一个新的方法:
    add_subparsers([title][, description][, prog][, parser_class][, action][, option_string][, dest][, help][, metavar])

    • title - 在输出的帮助中子解析器组的标题;默认情况下,如果提供description参数则为“subcommands”,否则使用位置参数的标题
    • description - 在输出的帮助中子解析器组的描述,默认为None
    • prog - 与子命令的帮助一起显示的使用帮助信息,默认为程序的名字和子解析器参数之前的所有位置参数
    • parser_class - 用于创建子解析器实例的类,默认为当前的解析器(例如ArgumentParser)
    • dest - 子命令的名字应该存储的属性名称;默认为None且不存储任何值
    • help - 在输出的帮助中子解析器中的帮助信息,默认为None
    • metavar - 在帮助中表示可用的子命令的字符串;默认为None并以{cmd1, cmd2, ..}的形式表示子命令
    parser = argparse.ArgumentParser()
    parser.add_argument('--foo', action='store_true', help='foo help')
    
    subparsers = parser.add_subparsers(help='sub-command help')
    parser_a = subparsers.add_parser('a', help='a help')
    parser_a.add_argument('bar', type=int, help='bar help')
    
    parser_b = subparsers.add_parser('b', help='b help')
    parser_b.add_argument('--baz', choices='XYZ', help='baz help')
    parser.parse_args(['a', '12'])
    Namespace(bar=12, foo=False)
    parser.parse_args(['--foo', 'b', '--baz', 'Z'])
    Namespace(baz='Z', foo=True)
    

    处理子命令的一个特别有效的方法是将add_subparsers()方法和set_defaults() 调用绑在一起使用,这样每个子命令就可以知道它应该执行哪个Python 函数。例如:

    
    def foo(args):
        print(args.x * args.y)
    def bar(args):
        print('((%s))' % args.z)
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()
    
    parser_foo = subparsers.add_parser('foo')
    parser_foo.add_argument('-x', type=int, default=1)
    parser_foo.add_argument('y', type=float)
    parser_foo.set_defaults(func=foo)
    
    parser_bar = subparsers.add_parser('bar')
    parser_bar.add_argument('z')
    parser_bar.set_defaults(func=bar)
    

    007.参数分组

    很多时候参数是相互配合使用的,这就可以用add_argument_group(title=None, description=None)分组

    parser = argparse.ArgumentParser(prog='PROG', add_help=False)
    group1 = parser.add_argument_group('group1', 'group1 description')
    group1.add_argument('foo', help='foo help')
    group2 = parser.add_argument_group('group2', 'group2 description')
    group2.add_argument('--bar', help='bar help')
    

    008.多级子命令

    如果我们的命令行工具有更加复杂的子命令解析需求,那么我们可以使用如下的方式做扩展:

    class Command:
    
        def __init__(self, argv):
            parser = argparse.ArgumentParser(
                description='xxxxxx',
                usage='''xxxx <command> [<args>]
    
    The most commonly used xxx commands are:
    
       clean       clean a project
       install     install a package
    
    
    ''')
            parser.add_argument('command', help='Subcommand to run')
            # parse_args defaults to [1:] for args, but you need to
            # exclude the rest of the args too, or validation will fail
            self.argv = argv
            args = parser.parse_args(argv[0:1])
            if not hasattr(self, args.command):
                print('Unrecognized command')
                parser.print_help()
                exit(1)
            # use dispatch pattern to invoke method with same name
            getattr(self, args.command)()
    
    
        def clean(self):
            parser = argparse.ArgumentParser(
                description='clean a project')
            parser.add_argument(
                '-A', '--all', action='store_true')
            parser.set_defaults(func=clean)
            args = parser.parse_args(self.argv[1:])
            args.func(args)
    
        def install(self):
            parser = argparse.ArgumentParser(
                description='install a package for this project')
            parser.add_argument('packages', nargs='?', type=str, default="DEFAULT")
            parser.add_argument(
                '-D', '--dev', action='store_true')
            parser.add_argument(
                '-T', '--test', action='store_true')
            parser.add_argument(
                '-A', '--all', action='store_true')
            parser.set_defaults(func=install)
            args = parser.parse_args(self.argv[1:])
            args.func(args)
            print("install done!")
    
    def main(argv: Sequence[str]=sys.argv[1:]):
        Command(argv)
    

    03.其他命令行解析工具

    click是一个很符合Python编程风格的命令行解析,支持构建比较复杂的命令行工具。

    fire是谷歌开源的命令行解析工具,可以直接根据函数参数解析成命令行,自动生成

    docopt所见即所得的命令行解析工具,利用文件的__doc__,写完文档注释你的命令行解析也就实现了。

    用<>包裹表示参数,如果参数后面有...则表示参数是列表
    用[]包裹选项
    用()包裹必选内容
    用|区分选项

    下面是一个实例:

    %%writefile test/sqrt_doc.py
    #!/usr/bin/env python
    # coding:utf-8 
    u"""
    Usage: 
      test1.py [option] <num>...
      test1.py (-v|--version)
      test1.py (-a|--all)
      test1.py (-h|--help)
    
    
    Options:
      -h --help      帮助
      -v --version   显示版本号.
      -a --all       显示全部参数
    """
    
    from docopt import docopt
    from math import sqrt
    __version__="0.1.0"
    
    
    
    def version():
        return "version:"+__version__
    
    def main():
        args = docopt(__doc__)
    
        if args.get("-h") or args.get("-help"):
            print(__doc__)
        elif args.get("-v") or args.get("--version"):
            print(__version__)
        elif args.get("-a") or args.get("--all"):
            print(args)
        elif args.get("<num>"):
            print(" ".join(map(lambda x :str(sqrt(float(x))),args.get("<num>"))))
        else:
            print("wrong args!")
            print(__doc__)
    
    
    
    if __name__ == '__main__':
        main()
    
    Writing test/sqrt_doc.py
    

    04.命令行进度条

    tqdm是一个进度条工具,除了可以给命令行工具增加进度条看出进度外,还可以用于jupyter-notebook

    tqdm模块的tqdm类是这个包的核心,所有功能都是在它上面衍生而来

    tqdm类 可以包装可迭代对象,它的实例化参数有:

    • desc : str, optional 放在bar前面的描述字符串

    • total : int, optional 显示多长

    • leave : bool, optional 结束时时保留进度条的所有痕迹。

    • file : io.TextIOWrapper or io.StringIO, optional 输出到文件

    • ncols : int, optional 自定义宽度

    • mininterval : float, optional 更新最短时间

    • maxinterval : float, optional 更新最大时间

    • miniters : int, optional 每次更新最小值

    • ascii : bool, optional 使用ascii碼显示

    • disable : bool, optional 是否禁用整个progressbar

    • unit : str, optional 显示的更新单位

    • unit_scale : bool, optional 根据单位换算进度

    • dynamic_ncols : bool, optional 可以不断梗概ncols的环境

    • smoothing : float, optional 用于速度估计的指数移动平均平滑因子(在GUI模式中忽略)。范围从0(平均速度)到1(当前/瞬时速度)[默认值:0.3]。

    • bar_format : str, optional 指定自定义栏字符串格式。可能会影响性能

    • initial : int, optional 初始计数器值。重新启动进度条时有用[默认值:0]。

    • position : int, optional 指定打印此条的线偏移(从0开始)如果未指定,则为自动。用于一次管理多个条

    下面写几个程序实例

    # 900万次循环
    from tqdm import tqdm
    for i in tqdm(range(int(9e6)),desc="test:"):
        pass
    
    test:: 100%|████████████████████████████████████████████████████████████| 9000000/9000000 [00:01<00:00, 4813939.90it/s]
    
    for i in tqdm(range(int(9e6)),desc="test",dynamic_ncols=True):
        pass
    
    test: 100%|█████████████████████████████████████████████████████████████| 9000000/9000000 [00:01<00:00, 4998333.08it/s]
    
    import time
    # 使用with语句手动更新
    with tqdm(total=100) as bar:
        for i in range(10):
            time.sleep(0.5)
            bar.update(10)
    
    100%|████████████████████████████████████████████████████████████████████████████████| 100/100 [00:05<00:00, 19.94it/s]
    

    05.为命令行工具自动创建gui

    Gooey是一个可以将python命令行自动转成gui的工具,它依赖wxpython,下面给出的的例子是吧一个求开平方的命令行工具转化为gui工具

    这个工具貌似挺方便,以后简单的gui都不用写了,直接自动生成。

    %%writefile src/python/std/sqrt_std_gui.py
    #!/usr/bin/env python3
    import argparse
    from math import sqrt
    from gooey import Gooey, GooeyParser
    
    
    __version__="0.1.0"
    
    def sqrtarg(number):
        return sqrt(number)
    
    def version():
        return "version:"+__version__
    @Gooey(language='chinese')
    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("number", type=int, help=u"求开根的参数")
        parser.add_argument("-v","--version", help=u"查看版本号",action="store_true")
    
        args = parser.parse_args()
    
        if args.version:
            print(version())
        if args.number:
            print(sqrtarg(args.number))
    
    if __name__ == '__main__':
        main()
        
    

    06.命令行工具发布

    下面介绍一个用setup.py把脚本安装到python的脚本位置的例子

    from distutils.core import setup
    import os
    pathroot = os.path.split(os.path.realpath(__file__))[0]
    setup(
        name='sqrt_doc',
        version='0.1.0',
    
        scripts=[pathroot+'/sqrt_doc.py']
    )
    

    相关文章

      网友评论

          本文标题:python3 编写命令行工具

          本文链接:https://www.haomeiwen.com/subject/mhceqctx.html