借鉴 C 语言的历史,学习如何用 Python 编写有用的 CLI 程序。html
本文的目标很简单:帮助新的 Python 开发者了解一些关于命令行接口(CLI)的历史和术语,并探讨如何在 Python 中编写这些有用的程序。python
首先,从 Unix 的角度谈谈命令行界面设计。linux
Unix 是一种计算机操做系统,也是 Linux 和 macOS(以及许多其余操做系统)的祖先。在图形用户界面以前,用户经过命令行提示符与计算机进行交互(想一想现在的 Bash 环境)。在 Unix 下开发这些程序的主要语言是 C,它的功能很是强大。git
所以,咱们至少应该了解 C 程序的基础知识。程序员
假设你没有读过上面那个连接的内容,C 程序的基本架构是一个叫作 main
的函数,它的签名是这样的。github
int main(int argc, char **argv)
{
...
}
复制代码
对于 Python 程序员来讲,这应该不会显得太奇怪。C 函数首先有一个返回类型、一个函数名,而后是括号内的类型化参数。最后,函数的主体位于大括号之间。函数名 main
是运行时连接器(构造和运行程序的程序)如何决定从哪里开始执行你的程序。若是你写了一个 C 程序,而它没有包含一个名为 main
的函数,它将什么也作不了。伤心。数组
函数参数变量 argc
和 argv
共同描述了程序被调用时用户在命令行输入的字符串列表。在典型的 Unix 命名传统中,argc
的意思是“参数计数”,argv
的意思是“参数向量”。向量听起来比列表更酷,而 argl
听起来就像一个要勒死的求救声。咱们是 Unix 系统的程序员,咱们不求救。咱们让其余人哭着求救。ruby
$ ./myprog foo bar -x baz
复制代码
若是 myprog
是用 C 语言实现的,则 argc
的值是 5,而 argv
是一个有五个条目的字符指针数组。(不要担忧,若是这听起来过于技术,那换句话说,这是一个由五个字符串组成的列表。)向量中的第一个条目 argv[0]
是程序的名称。argv
的其他部分包含参数。bash
argv[0] == "./myprog"
argv[1] == "foo"
argv[2] == "bar"
argv[3] == "-x"
argv[4] == "baz"
/* 注:不是有效的 C 代码 */
复制代码
在 C 语言中,你有不少方法来处理 argv
中的字符串。你能够手动地循环处理数组 argv
,并根据程序的须要解释每一个字符串。这相对来讲比较简单,但会致使程序的接口截然不同,由于不一样的程序员对什么是“好”有不一样的想法。架构
include <stdio.h>
/* 一个打印 argv 内容的简单 C 程序。 */
int main(int argc, char **argv) {
int i;
for(i=0; i<argc; i++)
printf("%s\n", argv[i]);
}
复制代码
命令行武器库中的下一个武器是一个叫作 getopt 的 C 标准库函数。这个函数容许程序员解析开关,即前面带破折号的参数(好比 -x
),而且能够选择将后续参数与它们的开关配对。想一想 /bin/ls -alSh
这样的命令调用,getopt
就是最初用来解析该参数串的函数。使用 getopt
使命令行的解析变得至关简单,并改善了用户体验(UX)。
include <stdio.h>
#include <getopt.h>
#define OPTSTR "b:f:"
extern char *optarg;
int main(int argc, char **argv) {
int opt;
char *bar = NULL;
char *foo = NULL;
while((opt=getopt(argc, argv, OPTSTR)) != EOF)
switch(opt) {
case 'b':
bar = optarg;
break;
case 'f':
foo = optarg;
break;
case 'h':
default': fprintf(stderr, "Huh? try again."); exit(-1); /* NOTREACHED */ } printf("%s\n", foo ? foo : "Empty foo"); printf("%s\n", bar ? bar : "Empty bar"); } 复制代码
就我的而言,我但愿 Python 有开关,但这永远、永远不会发生。
GNU 项目出现了,并为他们实现的传统 Unix 命令行工具引入了更长的格式参数,好比--file-format foo
。固然,咱们这些 Unix 程序员很讨厌这样,由于打字太麻烦了,可是就像咱们这些旧时代的恐龙同样,咱们输了,由于用户喜欢更长的选项。我历来没有写过任何使用 GNU 风格选项解析的代码,因此这里没有代码示例。
GNU 风格的参数也接受像 -f foo
这样的短名,也必须支持。全部这些选择都给程序员带来了更多的工做量,由于他们只想知道用户要求的是什么,而后继续进行下去。但用户获得了更一致的用户体验:长格式选项、短格式选项和自动生成的帮助,使用户没必要再试图阅读臭名昭著的难以解析的手册页面(参见 ps 这个特别糟糕的例子)。
你如今已经接触了足够多(太多?)的命令行的历史,对如何用咱们最喜欢的语言来编写 CLI 有了一些背景知识。Python 在命令行解析方面给出了相似的几个选择:本身解析,自给自足的方式,以及大量的第三方方式。你选择哪种取决于你的特定状况和需求。
你能够从 sys 模块中获取程序的参数。
import sys
if __name__ == '__main__':
for value in sys.argv:
print(value)
复制代码
在 Python 标准库中已经有几个参数解析模块的实现:getopt、optparse,以及最近的 argparse。argparse
容许程序员为用户提供一致的、有帮助的用户体验,但就像它的 GNU 前辈同样,它须要程序员作大量的工做和“模板代码”才能使它“奏效”。
from argparse import ArgumentParser
if __name__ == "__main__":
argparser = ArgumentParser(description='My Cool Program')
argparser.add_argument("--foo", "-f", help="A user supplied foo")
argparser.add_argument("--bar", "-b", help="A user supplied bar")
results = argparser.parse_args()
print(results.foo, results.bar)
复制代码
好处是当用户调用 --help
时,有自动生成的帮助。可是自给自足的优点呢?有时,你的项目状况决定了你对第三方库的访问是有限的,或者说是没有,你不得不用 Python 标准库来“凑合”。
而后是 Click。Click
框架使用装饰器的方式来构建命令行解析。忽然间,写一个丰富的命令行界面变得有趣而简单。在装饰器的酷炫和将来感的使用下,不少复杂的东西都消失了,用户惊叹于自动支持关键字补完以及上下文帮助。全部这些都比之前的解决方案写的代码更少。任什么时候候,只要你能写更少的代码,还能把事情作好,就是一种胜利。而咱们都想要胜利。
import click
@click.command()
@click.option("-f", "--foo", default="foo", help="User supplied foo.")
@click.option("-b", "--bar", default="bar", help="User supplied bar.")
def echo(foo, bar):
"""My Cool Program It does stuff. Here is the documentation for it. """
print(foo, bar)
if __name__ == "__main__":
echo()
复制代码
你能够在 @click.option
装饰器中看到一些与 argparse
相同的模板代码。可是建立和管理参数分析器的“工做”已经被抽象化了。如今,命令行参数被解析,而值被赋给函数参数,从而函数 echo
被魔法般地调用。
在 Click
接口中添加参数就像在堆栈中添加另外一个装饰符并将新的参数添加到函数定义中同样简单。
Typer 创建在 Click
之上,是一个更新的 CLI 框架,它结合了 Click
的功能和现代 Python 类型提示。使用 Click
的缺点之一是必须在函数中添加一堆装饰符。CLI 参数必须在两个地方指定:装饰符和函数参数列表。Typer
免去你造轮子 去写 CLI 规范,让代码更容易阅读和维护。
import typer
cli = typer.Typer()
@cli.command()
def echo(foo: str = "foo", bar: str = "bar"):
"""My Cool Program It does stuff. Here is the documentation for it. """
print(foo, bar)
if __name__ == "__main__":
cli()
复制代码
哪一种方法是正确的?这取决于你的用例。你是在写一个只有你才会使用的快速而粗略的脚本吗?直接使用 sys.argv
而后继续编码。你须要更强大的命令行解析吗?也许 argparse
就够了。你是否有不少子命令和复杂的选项,你的团队是否会天天使用它?如今你必定要考虑一下 Click
或 Typer
。做为一个程序员的乐趣之一就是魔改出替代实现,看看哪个最适合你。
最后,在 Python 中有不少用于解析命令行参数的第三方软件包。我只介绍了我喜欢或使用过的那些。你喜欢和/或使用不一样的包是彻底能够的,也是咱们所指望的。个人建议是先从这些包开始,而后看看你最终的结果。
去写一些很酷的东西吧。
via: opensource.com/article/20/…
做者:Erik O'Shaughnessy 选题:lujun9972 译者:wxy 校对:wxy