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

所谓子命令,就是类似于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)  # 跳转到对应的函数


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352