给Python脚本带上子命令(sub-commands)

2020-02-06  本文已影响0人  钢琴师2005

所谓子命令,就是类似于python的包管理工具pip,不止可以接受参数,还可以拥有子命令,子命令可以单独接受特有参数,这为构建cli(commandline interface)程序提供了莫大的帮助,把命令分门别类可以提高程序的可读性和操作的便捷性。

之前写cli程序,都是接受一级参数,所有参数都是-a、-b这样的然后在程序中进行判断和识别,包括冲突选项的排除都得自己做,这样导致入口程序代码过长不说,因为每次写的都不一样导致代码不规范不统一,质量参差不齐,而且本来自己写的质量就差😥

使用效果

主入口
子命令connect

参考pip

先看看优秀的子命令程序是怎么样的,以pip为例。

参数

pip -V:查看版本

~$ pip3 -V
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

pip -h:查看帮助

~$ pip3 -h

Usage:
  pip3 <command> [options]

Commands:
  install                     Install packages.
  download                    Download packages.
  uninstall                   Uninstall packages.
  ......
  help                        Show help for commands.

General Options:
  -h, --help                  Show help.
  --isolated                  Run pip in an isolated mode, ignoring environment variables and user configuration.
  -v, --verbose               Give more output. Option is additive, and can be used up to 3 times.
  -V, --version               Show version and exit.
  ......

上面-h的输出也提示了其子命令为installdownloaduninstall等。

子命令

pip install:安装包,通过-h我们可以看到,总共有5中用法,其中包含了常用的:

pip install <package name> [options]:通过包名安装

pip install -r requirements.txt [options]:通过依赖文件安装

~$ pip3 install -h

Usage:
  pip3 install [options] <requirement specifier> [package-index-options] ...
  pip3 install [options] -r <requirements file> [package-index-options] ...
  ......

Description:
  ......

  pip also supports installing from "requirements files", which provide
  an easy way to specify a whole environment to be installed.

Install Options:
  -r, --requirement <file>    Install from the given requirements file. This option can be used multiple times.
  ......
General Options:
  ......

可以单独接收与pip不同的参数和位置参数,相当于一个独立的命令,这就是子命令存在的意义。

具体实现

本文用到了python3内置库argparse,官方文档https://docs.python.org/3/library/argparse.html,具体用法可以到上面一点点摸索,因为本文不涉及到argparse的所有用法。

基本入口

首先引用类argparse.ArgumentParser(),只需要填写其description参数:

# _*_coding:utf-8 _*_
# @Time    : 2020/2/5 18:41
# @Author  : Shek 
# @FileName: OneWayPipe_CLI.py
# @Software: PyCharm
import argparse

parser = argparse.ArgumentParser(description='你的程序描述')

创建子命令管理器

接下来与普通的命令行程序直接调用add_argument()方法不同,我们由于是要构建包含子命令的程序,故应该调用add_subparsers()方法并赋值给subparsers变量:

subparsers = parser.add_subparsers()

注意,只允许出现一次parser.add_subparsers(),因为这是其他子命令的集合,是子命令的BOSS

添加子命令bind

接下来BOSS可以具体分配子命令了,使用add_parser()方法并赋值即可,如这里的bind子命令

# command 'bind'
cmd_bind = subparsers.add_parser('bind', help='bind server')

这时候cmd_bind就可以继续添加只对其有效的参数了,使用add_argument()即可,如我需要两个参数protocol和addr,要求不输入的时候使用默认值,然后使用固定的位置来填入参数:

cmd_bind.add_argument('protocol', action='store', nargs='?', default='tcp', help='communicate protocol')
cmd_bind.add_argument('addr', action='store', nargs='?', default='*:4000', help='<host>:<port>')

nargs:如果要不输入时就为默认值的话是必填项,赋值固定为nargs='?'

action'store'代表存储输入的值并赋值到参数中,如addr参数在参数集args中的引用方式为args.addr,除此以外还有'store_true''store_false',分别代表赋值为TrueFalse

help:-h时显示的帮助文本

default:默认值

获取参数后需指定一个函数来处理,构建一个函数sub_cmd_bind(arguments)

def sub_cmd_bind(arguments):
    print('you are attempting to bind {}://{}'.format(arguments.protocol, arguments.addr))

并用set_defaults()方法指定到cmd_bind

cmd_bind.set_defaults(func=sub_cmd_bind)

到此对bind子命令的配置就完成了

添加子命令connect

同理得:

子命令connect配置代码
不同的是cmd_connect中多了一个--keep-alive参数,这个参数是布尔类型的,加了就是True,不加就是False,另外带减号”-“的参数在引用的时候都要被转义成下划线”_“,如

--keep-alive:使用时为arguments.keep_alive

无参数时跳转

子命令都完事儿了,现在需要加一行代码让程序对输入的参数跳转到指定的函数去(前面只是设置了各子命令的默认函数,但是程序还没有允许你直接跳过去):

args = parser.parse_args()  # 处理输入的参数
args.func(args)  # 跳转到对应的函数

这样就可以了,但是存在一个瑕疵就是不带参数直接运行python chat.py时会报错:

触发AttributeError
说的是最后一行代码,因为没有输入参数,所以没func这个特性(也就是没生成这个函数让你去用啦),中间加一句判断:
if not hasattr(args, 'func'):
    # 无参数时跳转到-h
    args = parser.parse_args(['-h'])

这样就完成了。

完整实现

# _*_coding:utf-8 _*_
# @Time    : 2020/2/5 18:41
# @Author  : Shek 
# @FileName: OneWayPipe_CLI.py
# @Software: PyCharm
import argparse


def sub_cmd_bind(arguments):
    print('you are attempting to bind {}://{}'.format(arguments.protocol, arguments.addr))


def sub_cmd_connect(arguments):
    print('you are attempting to connect {}://{}'.format(arguments.protocol, arguments.addr))
    if arguments.keep_alive:
        print('automatically reconnect enabled')


parser = argparse.ArgumentParser(
    description='A LAN-chat program written in Python3 http://github.com/YourGithub/RepositoryAddress')
subparsers = parser.add_subparsers()

# command 'bind'
cmd_bind = subparsers.add_parser('bind', help='bind server')
cmd_bind.add_argument('protocol', action='store', nargs='?', default='tcp', help='communicate protocol')
cmd_bind.add_argument('addr', action='store', nargs='?', default='*:4000', help='<host>:<port>')
cmd_bind.set_defaults(func=sub_cmd_bind)

# command 'connect'
cmd_connect = subparsers.add_parser('connect', help='connect to a server')
cmd_connect.add_argument('protocol', action='store', nargs='?', default='tcp', help='communicate protocol')
cmd_connect.add_argument('addr', action='store', nargs='?', default='127.0.0.1:4000', help='<host>:<port>')
cmd_connect.add_argument('--keep-alive', action='store_true', help='automatically reconnect when corrupted')
cmd_connect.set_defaults(func=sub_cmd_connect)

args = parser.parse_args()  # 处理输入的参数
if not hasattr(args, 'func'):
    # 无参数时跳转到-h,否则会提示 namespace object has not attribute 'func',故这里用hasattr()判断
    args = parser.parse_args(['-h'])
args.func(args)  # 跳转到对应的函数


Vultr 2020年优惠:注册就送100美金,领取你的第一台VPS
上一篇下一篇

猜你喜欢

热点阅读