命令行程序对比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']
)