python 命令行界面
本文有一个简单的目标:帮助新Python开发人员掌握一些有关命令行界面 (CLI)的历史和术语,并探索如何用Python编写这些有用的程序。
在一开始的时候…
首先, Unix对命令行界面设计的看法。
Unix是计算机操作系统,是Linux和macOS(以及许多其他操作系统)的始祖。 在使用图形用户界面之前,用户是通过命令行提示符(想想当今的Bash环境)与计算机进行交互的。 在Unix下开发这些程序的主要语言是C ,它的功能非常强大 。
因此,我们至少应该了解C程序的基础。
假设您没有阅读该链接,则C程序的基本体系结构是一个称为main
的函数,其签名如下所示:
int main
(
int argc
,
char
** argv
)
{
...
}
对于Python程序员来说,这看起来并不奇怪。 C函数首先有一个返回类型,一个函数名称,然后是括号内的键入参数。 最后,函数的主体位于花括号之间。 函数名称main
是运行时链接程序(构造和运行程序的程序)如何决定从哪里开始执行程序的方式。 如果您编写的C程序不包含名为main
的函数,则它将不会执行任何操作。 伤心。
argc
和argv
一起描述了用户在调用程序时在命令行上键入的字符串列表。
在典型的简短Unix命名传统中, argc
表示参数计数 , argv
表示参数向量 。
Vector听起来比list凉爽,而argl
听起来像是勒死的argl
。
我们是Unix系统程序员,我们不求助。
我们让其他人哭泣寻求帮助。
继续
$ . / myprog foo bar -x baz
如果myprog
是用C实现的,则argc
的值为5,而argv
是指向具有五个条目的字符的指针数组。 (不要担心这听起来是否太技巧性了;它是五个字符串的列表。)向量的第一个条目argv[0]
是程序的名称。 argv
的其余部分包含参数:
argv
[
0
]
==
"./myprog"
argv
[
1
]
==
"foo"
argv
[
2
]
==
"bar"
argv
[
3
]
==
"-x"
argv
[
4
]
==
"baz"
/* Note: not valid C */
在C语言中,您有很多选择来处理argv
的字符串。 您可以手动遍历数组argv
并根据程序需要解释每个字符串。 这是相对容易的,但是由于不同的程序员对“好”有不同的想法,因此导致程序具有截然不同的接口。
include
< stdio.
h
>
/* A simple C program that prints the contents of argv */
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" ,
类的命令调用/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能够switch
,但这永远不会发生 。
GNU一代
GNU项目随之而来,并为传统Unix命令行工具的实现引入了更长的格式参数,例如--file-format foo
。 当然,我们Unix程序员讨厌这种输入方式,因为输入的内容太多,但是像恐龙一样,我们输了,是因为用户喜欢更长的选项。 我从未使用GNU样式的选项解析编写任何代码,因此这里没有代码示例。
GNU风格的参数也接受必须支持的短名称,例如-f foo
。 所有这些选择为程序员带来了更多的工作量,他们只是想知道用户的要求并继续进行下去。 但是用户获得了更加一致的用户体验:长格式和短格式选项以及自动生成的帮助,这些帮助通常使用户无法尝试阅读臭名昭著的难于解析的手册页(有关特别糟糕的示例,请参阅ps
)。
但是我们在谈论Python吗?
您现在已经了解了足够多(太多?)命令行历史记录,以获取有关如何使用我们喜欢的语言编写CLI的一些信息。 Python提供了许多类似的命令行解析选择。 自己动手,包含电池的选件和大量第三方选件。 选择哪一种取决于您的特定情况和需求。
首先,自己动手
您可以从sys
模块获取程序的参数。
import
sys
if __name__
==
'__main__' :
for value
in
sys .
argv :
print
( value
)
含电池
Python标准库中有几种参数解析模块的实现。 getopt
, optparse
和最近的argparse
。 Argparse
允许程序员为用户提供一致且有用的UX,但是像其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标准库。
CLI的现代方法
然后是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中解析命令行参数。 我只介绍了我喜欢或曾经使用过的那些。 完全可以,希望您喜欢和/或使用其他软件包。 我的建议是从这些开始,看看最终的结果。
去写一些很酷的东西。
这篇文章最初出现在PyBites上 ,经许可重新发布。
python 命令行界面