Python 命令行参数

举报
Yuchuan 发表于 2021/12/26 06:26:55 2021/12/26
【摘要】 在本教程中,您浏览了 Python 命令行参数的许多不同方面。您应该准备好将以下技能应用到您的代码中: Python 命令行参数的约定和伪标准 在起源的sys.argv在Python 的用法为sys.argv运行 Python 程序提供灵活性 的Python标准库像argparse或getopt抽象命令行处理 在强大的Python包一样click,并python_toolkit进一步提高了程序的

目录

添加处理Python 命令行参数的功能为基于文本的命令行程序提供了一个用户友好的界面。它类似于图形用户界面,用于由图形元素或小部件操作的可视化应用程序。

Python 公开了一种机制来捕获和提取 Python 命令行参数。这些值可用于修改程序的行为。例如,如果您的程序处理从文件读取的数据,那么您可以将文件名传递给您的程序,而不是在源代码中硬编码该值。

在本教程结束时,您将知道:

  • Python 命令行参数的起源
  • 对 Python 命令行参数的底层支持
  • 指导命令行界面设计的标准
  • 手动自定义和处理 Python 命令行参数的基础知识
  • Python 中可用的库可简化复杂命令行界面的开发

如果您想要一种用户友好的方式为您的程序提供 Python 命令行参数,而无需导入专用库,或者如果您想更好地了解专用于构建 Python 命令行界面的现有库的通用基础,那么请保留在阅读!

命令行界面

命令行界面(CLI)提供了用于用户与在基于文本的运行的程序进行交互的方式解释器。shell 解释器的一些示例是Linux上的Bash或Windows上的命令提示符。命令行界面由暴露命令提示符的 shell 解释器启用。它可以通过以下要素来表征:

  • 命令或程序
  • 零个或多个命令行参数
  • 输出表示所述命令的结果
  • 文本文档称为用法帮助

并非每个命令行界面都可以提供所有这些元素,但此列表也并非详尽无遗。命令行的复杂性范围从传递单个参数的能力到多个参数和选项,很像域特定语言。例如,某些程序可能会从命令行启动 Web 文档或启动像 Python 这样的交互式 shell 解释器

以下两个带有 Python 命令的示例说明了命令行界面的描述:

$ python -c "print('Real Python')"
Real Python

在该第一示例,Python解释需要选项-c命令,它说以下选项来执行的Python命令行参数-c作为Python程序。

另一个示例显示了如何调用 Python-h来显示帮助:

$ python -h
usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-b     : issue warnings about str(bytes_instance), str(bytearray_instance)
         and comparing bytes/bytearray with str. (-bb: issue errors)
[ ... complete help text not shown ... ]

在您的终端中尝试此操作以查看完整的帮助文档。

The C Legacy

Python 命令行参数直接继承自C编程语言。正如Guido Van Rossum于 1993 年在An Introduction to Python for Unix/C Programmers中所写的那样,C 对 Python 有很大的影响。Guido 提到了文字、标识符、运算符和语句(如breakcontinue、 或 )的定义return。Python 命令行参数的使用也受到 C 语言的强烈影响。

为了说明相似之处,请考虑以下 C 程序:

 1// main.c
 2#include <stdio.h>
 3
 4int main(int argc, char *argv[]) {
 5    printf("Arguments count: %d\n", argc);
 6    for (int i = 0; i < argc; i++) {
 7        printf("Argument %6d: %s\n", i, argv[i]);
 8    }
 9    return 0;
10}

第 4 行定义了main(),它是 C 程序的入口点。注意参数:

  1. argc 是一个整数,表示程序的参数数量。
  2. argv 是一个指向字符的指针数组,在数组的第一个元素中包含程序的名称,然后是程序的参数(如果有的话),在数组的其余元素中。

你可以在 Linux 上用 编译上面的代码gcc -o main main.c,然后执行./main得到以下内容:

$ gcc -o main main.c
$ ./main
Arguments count: 1
Argument      0: ./main

除非在与选项在命令行明确表示-oa.out是由生成的可执行文件的默认名称的gcc编译器。它代表汇编器输出,让人联想到在较旧的 UNIX 系统上生成的可执行文件。请注意,可执行文件的名称./main是唯一的参数。

让我们通过将几个 Python 命令行参数传递给同一个程序来为这个例子增添趣味:

$ ./main Python Command Line Arguments
Arguments count: 5
Argument      0: ./main
Argument      1: Python
Argument      2: Command
Argument      3: Line
Argument      4: Arguments

输出显示参数的数量是5,参数列表包括程序的名称main,后跟"Python Command Line Arguments"您在命令行中传递的短语的每个单词。

注意argc代表参数计数,而argv代表参数向量。要了解更多信息,请查看A Little C Primer/C Command Line Arguments

编译main.c假设您使用的是 Linux 或 Mac OS 系统。在 Windows 上,您还可以使用以下选项之一编译此 C 程序:

如果您已经安装了 Microsoft Visual Studio 或 Windows Build Tools,那么您可以main.c按如下方式进行编译:

C:/>cl main.c

您将获得一个名为的可执行文件main.exe,您可以从该文件开始:

C:/>main
Arguments count: 1
Argument      0: main

您可以实现一个 Python 程序,main.py,相当于main.c您在上面看到的 C 程序,:

# main.py
import sys

if __name__ == "__main__":
    print(f"Arguments count: {len(sys.argv)}")
    for i, arg in enumerate(sys.argv):
        print(f"Argument {i:>6}: {arg}")

您看不到C 代码示例中的argc 变量。它在 Python 中不存在,因为sys.argv已经足够了。您可以在sys.argv不知道列表长度的情况下解析 Python 命令行参数,并且len()如果您的程序需要参数数量,您可以调用内置函数。

另请注意enumerate(),当应用于可迭代enumerate对象时,返回一个对象,该对象可以发出将元素索引sys.arg与其对应值相关联的对。这允许循环遍历 的内容,sys.argv而无需维护列表中索引的计数器。

执行main.py如下:

$ python main.py Python Command Line Arguments
Arguments count: 5
Argument      0: main.py
Argument      1: Python
Argument      2: Command
Argument      3: Line
Argument      4: Arguments

sys.argv 包含与 C 程序中相同的信息:

  • 程序名称 main.py是列表的第一项。
  • 参数 PythonCommandLineArguments是列表中的其余元素。

通过对 C 语言一些神秘方面的简短介绍,您现在掌握了一些宝贵的知识,可以进一步掌握 Python 命令行参数。

来自 Unix 世界的两个实用程序

要在本教程中使用 Python 命令行参数,您将实现来自 Unix 生态系统的两个实用程序的部分功能:

  1. sha1sum
  2. 序列

在以下部分中,您将熟悉这些 Unix 工具。

sha1sum

sha1sum计算SHA-1 哈希值,通常用于验证文件的完整性。对于给定的输入,哈希函数始终返回相同的值。输入中的任何细微更改都将导致不同的哈希值。在使用带有具体参数的实用程序之前,您可以尝试显示帮助:

$ sha1sum --help
Usage: sha1sum [OPTION]... [FILE]...
Print or check SHA1 (160-bit) checksums.

With no FILE, or when FILE is -, read standard input.

  -b, --binary         read in binary mode
  -c, --check          read SHA1 sums from the FILEs and check them
      --tag            create a BSD-style checksum
  -t, --text           read in text mode (default)
  -z, --zero           end each output line with NUL, not newline,
                       and disable file name escaping
[ ... complete help text not shown ... ]

显示命令行程序的帮助是命令行界面中公开的常见功能。

要计算文件内容的 SHA-1 哈希值,请按以下步骤操作:

$ sha1sum main.c
125a0f900ff6f164752600550879cbfabb098bc3  main.c

结果显示 SHA-1 哈希值作为第一个字段,文件名作为第二个字段。该命令可以将多个文件作为参数:

$ sha1sum main.c main.py
125a0f900ff6f164752600550879cbfabb098bc3  main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4  main.py

由于 Unix 终端的通配符扩展功能,还可以提供带有通配符的 Python 命令行参数。星号或星号 ( *)就是这样的一个字符:

$ sha1sum main.*
3f6d5274d6317d580e2ffc1bf52beee0d94bf078  main.c
f41259ea5835446536d2e71e566075c1c1bfc111  main.py

shell 转换main.*main.cmain.py,这是main.*当前目录中与模式匹配的两个文件,并将它们传递给sha1sum. 该程序计算参数列表中每个文件的SHA1 哈希值。您会看到,在 Windows 上,行为是不同的。Windows 没有通配符扩展,因此程序可能必须适应它。您的实现可能需要在内部扩展通配符。

没有任何参数,sha1sum从标准输入读取。您可以通过在键盘上键入字符来向程序提供数据。输入可以包含任何字符,包括回车Enter。要终止输入,则必须将信号文件的末尾Enter,其次是序列Ctrl+D

 1$ sha1sum
 2Real
 3Python
 487263a73c98af453d68ee4aab61576b331f8d9d6  -

您首先输入程序的名称sha1sum,然后是Enter,然后是RealPython,每一个后面都跟有Enter。要关闭输入流,请键入Ctrl+D。结果是为 text 生成的 SHA1 哈希值Real\nPython\n。该文件的名称是-. 这是指示标准输入的约定。执行以下命令时哈希值相同:

$ python -c "print('Real\nPython\n', end='')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6  -
$ python -c "print('Real\nPython')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6  -
$ printf "Real\nPython\n" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6  -

接下来,您将阅读seq.

seq

seq生成一串数字。在最基本的形式中,例如生成从 1 到 5 的序列,您可以执行以下操作:

$ seq 5
1
2
3
4
5

seq大致了解 公开的可能性,您可以在命令行显示帮助:

$ seq --help
Usage: seq [OPTION]... LAST
  or:  seq [OPTION]... FIRST LAST
  or:  seq [OPTION]... FIRST INCREMENT LAST
Print numbers from FIRST to LAST, in steps of INCREMENT.

Mandatory arguments to long options are mandatory for short options too.
  -f, --format=FORMAT      use printf style floating-point FORMAT
  -s, --separator=STRING   use STRING to separate numbers (default: \n)
  -w, --equal-width        equalize width by padding with leading zeroes
      --help     display this help and exit
      --version  output version information and exit
[ ... complete help text not shown ... ]

在本教程中,你会写几个简单的变体sha1sumseq。在每个示例中,您将了解有关 Python 命令行参数的不同方面或功能组合。

在Mac OS和Linux,sha1sum并且seq应该将预安装,虽然功能和帮助信息有时系统或分布之间略有不同。如果您使用的是 Windows 10,那么最方便的方法是在安装在WSL上的 Linux 环境中运行sha1sum和运行。如果您无权访问暴露标准 Unix 实用程序的终端,那么您可以访问在线终端:seq

  • PythonAnywhere创建一个免费帐户并启动 Bash 控制台。
  • repl.it创建一个临时 Bash 终端。

这是两个示例,您可能还会找到其他示例。

sys.argv阵列

在探索一些公认的约定并发现如何处理 Python 命令行参数之前,您需要知道对所有 Python 命令行参数的底层支持是由sys.argv. 以下部分中的示例向您展示了如何处理存储在其中的 Python 命令行参数,sys.argv以及如何克服尝试访问它们时出现的典型问题。你会学到:

  • 如何访问内容sys.argv
  • 如何减轻全球性的副作用sys.argv
  • 如何处理Python 命令行参数中的空格
  • 如何在访问 Python 命令行参数时处理错误
  • 如何摄取按字节传递的 Python 命令行参数的原始格式

让我们开始吧!

显示参数

sys模块公开一个名为的数组argv,其中包括以下内容:

  1. argv[0] 包含当前 Python 程序的名称。
  2. argv[1:],列表的其余部分包含传递给程序的任何和所有 Python 命令行参数。

以下示例演示了 的内容sys.argv

 1# argv.py
 2import sys
 3
 4print(f"Name of the script      : {sys.argv[0]=}")
 5print(f"Arguments of the script : {sys.argv[1:]=}")

下面是这段代码的工作原理:

  • 第 2 行导入内部 Python 模块sys
  • 第 4 行通过访问列表的第一个元素提取程序的名称sys.argv
  • 第 5行通过获取列表的所有剩余元素显示 Python 命令行参数sys.argv

注意: 中使用的f-string语法argv.py利用了 Python 3.8 中的新调试说明符。要了解有关此新 f-string 功能和其他功能的更多信息,请查看Python 3.8 中的酷新功能

如果您的 Python 版本低于 3.8,那么只需删除=两个 f 字符串中的等号 ( ) 即可让程序成功执行。输出将只显示变量的值,而不是它们的名称。

argv.py使用任意参数列表执行上面的脚本,如下所示:

$ python argv.py un deux trois quatre
Name of the script      : sys.argv[0]='argv.py'
Arguments of the script : sys.argv[1:]=['un', 'deux', 'trois', 'quatre']

输出确认 的内容sys.argv[0]是 Python 脚本argv.py,并且sys.argv列表的其余元素包含脚本的参数['un', 'deux', 'trois', 'quatre']

总而言之,sys.argv包含所有argv.pyPython 命令行参数。当 Python 解释器执行 Python 程序时,它会解析命令行并填充sys.argv参数。

反转第一个参数

既然您有足够的背景知识sys.argv,您将要对命令行传递的参数进行操作。该示例reverse.py反转在命令行传递的第一个参数:

 1# reverse.py
 2
 3import sys
 4
 5arg = sys.argv[1]
 6print(arg[::-1])

reverse.py反转第一个参数的过程中,执行以下步骤:

  • 第 5 行获取存储在索引1处的程序的第一个参数sys.argv。请记住,程序名称存储在索引0sys.argv
  • 第 6 行打印反转的字符串。args[::-1]是一种使用切片操作反转列表的 Pythonic 方式。

您按如下方式执行脚本:

$ python reverse.py "Real Python"
nohtyP laeR

正如预期的那样,对 output 的唯一参数进行reverse.py操作"Real Python"并反转"nohtyP laeR"。请注意,"Real Python"用引号将多字字符串括起来可确保解释器将其作为唯一参数处理,而不是两个参数。您将在后面的部分深入研究参数分隔符

变异 sys.argv

sys.argv全球可用您运行的Python程序。在流程执行期间导入的所有模块都可以直接访问sys.argv. 这种全局访问可能很方便,但sys.argv不是一成不变的。您可能希望实现一种更可靠的机制来向 Python 程序中的不同模块公开程序参数,尤其是在具有多个文件的复杂程序中。

观察如果你篡改会发生什么sys.argv

# argv_pop.py

import sys

print(sys.argv)
sys.argv.pop()
print(sys.argv)

您调用.pop()以删除并返回 中的最后一项sys.argv

执行上面的脚本:

$ python argv_pop.py un deux trois quatre
['argv_pop.py', 'un', 'deux', 'trois', 'quatre']
['argv_pop.py', 'un', 'deux', 'trois']

请注意,第四个参数不再包含在sys.argv.

在简短的脚本中,您可以放心地依赖对 的全局访问sys.argv,但在较大的程序中,您可能希望将参数存储在单独的变量中。前面的例子可以修改如下:

# argv_var_pop.py

import sys

print(sys.argv)
args = sys.argv[1:]
print(args)
sys.argv.pop()
print(sys.argv)
print(args)

这一次,虽然sys.argv失去了最后的元素,args却被安全地保存了下来。args不是全局的,您可以传递它来解析每个程序逻辑的参数。Python 包管理器pip使用这种方法。这是pip源代码的简短摘录:

def main(args=None):
    if args is None:
        args = sys.argv[1:]

在此摘自pip源代码的代码片段中,main()保存到仅包含参数而不包含文件名args的片段中sys.argvsys.argv保持不变,不受args任何无意更改的影响sys.argv

转义空白字符

reverse.py之前看到的示例中,第一个也是唯一的参数是"Real Python",结果是"nohtyP laeR"。该参数在"Real"和之间包含一个空格分隔符"Python",并且需要对其进行转义。

在 Linux 上,可以通过执行以下操作之一来转义空格:

  1. 周围用单引号的参数('
  2. 用双引号 ( ")包围参数
  3. 用反斜杠 ( \)前缀每个空格

如果没有转义解决方案之一,则reverse.py存储两个参数"Real"insys.argv[1]"Python"in sys.argv[2]

$ python reverse.py Real Python
laeR

上面的输出显示脚本仅反转"Real"并且"Python"被忽略。为确保存储两个参数,您需要用双引号 ( ")将整个字符串括起来。

您还可以使用反斜杠 ( \) 来转义空格:

$ python reverse.py Real\ Python
nohtyP laeR

使用反斜杠 ( \),命令 shell 向 Python 公开一个唯一参数,然后向reverse.py.

在 Unix shell 中,内部字段分隔符 (IFS)定义用作分隔符的字符。IFS可以通过运行以下命令来显示shell 变量的内容:

$ printf "%q\n" "$IFS"
$' \t\n'

从上面的结果中' \t\n',您确定了三个分隔符:

  1. 空格' ')
  2. 标签\t)
  3. 换行符\n)

用反斜杠 ( \)前缀空格会绕过空格作为字符串中的分隔符的默认行为"Real Python"。这会按预期生成一个文本块,而不是两个。

请注意,在 Windows 上,可以使用双引号组合来管理空白解释。这有点违反直觉,因为在 Windows 终端中,双引号 ( ") 被解释为禁用和随后启用特殊字符(如空格制表符管道|))的开关。

因此,当您用双引号将多个字符串括起来时,Windows 终端会将第一个双引号解释为忽略特殊字符的命令,将第二个双引号解释解释特殊字符的命令

考虑到这些信息,可以安全地假设用双引号将多个字符串括起来会给您带来预期的行为,即将一组字符串作为单个参数公开。要确认双引号在 Windows 命令行上的这种特殊效果,请观察以下两个示例:

C:/>python reverse.py "Real Python"
nohtyP laeR

在上面的示例中,您可以直观地推断出"Real Python"被解释为单个参数。但是,请了解使用单双引号时会发生什么:

C:/>python reverse.py "Real Python
nohtyP laeR

命令提示符将整个字符串"Real Python"作为单个参数传递,就像参数是"Real Python". 实际上,Windows 命令提示符将唯一双引号视为一个开关,以禁用空格作为分隔符的行为,并将双引号后面的任何内容作为唯一参数传递。

有关 Windows 终端中双引号效果的更多信息,请查看更好的方式来理解 Windows 命令行参数的引用和转义

处理错误

Python 命令行参数是松散的字符串。许多事情都可能出错,因此最好为您的程序用户提供一些指导,以防他们在命令行中传递不正确的参数。例如,reverse.py期望一个参数,如果省略它,则会出现错误:

 1$ python reverse.py
 2Traceback (most recent call last):
 3  File "reverse.py", line 5, in <module>
 4    arg = sys.argv[1]
 5IndexError: list index out of range

引发了Python异常 IndexError,相应的回溯显示错误是由表达式引起的arg = sys.argv[1]。异常的消息是list index out of range。您没有在命令行传递参数,因此sys.argvindex的列表中没有任何内容1

这是一种常见的模式,可以通过几种不同的方式解决。对于这个初始示例,您将通过arg = sys.argv[1]try块中包含表达式来保持简短。修改代码如下:

 1# reverse_exc.py
 2
 3import sys
 4
 5try:
 6    arg = sys.argv[1]
 7except IndexError:
 8    raise SystemExit(f"Usage: {sys.argv[0]} <string_to_reverse>")
 9print(arg[::-1])

第 4 行的表达式包含在一个try块中。第 8 行引发了内置异常SystemExit。如果没有参数传递给reverse_exc.py,则该过程退出1后打印使用状态代码。请注意sys.argv[0]错误消息中的集成。它在使用消息中公开程序的名称。现在,当您在没有任何 Python 命令行参数的情况下执行相同的程序时,您可以看到以下输出:

$ python reverse.py
Usage: reverse.py <string_to_reverse>

$ echo $?
1

reverse.py没有在命令行传递参数。因此,程序会引发SystemExit错误消息。这将导致程序退出的状态为1,当你打印的特殊变量,它会显示$?echo

计算 sha1sum

您将编写另一个脚本来演示,在类 Unix系统上,Python 命令行参数是从操作系统按字节传递的。此脚本将字符串作为参数并输出该参数的十六进制SHA-1哈希值:

 1# sha1sum.py
 2
 3import sys
 4import hashlib
 5
 6data = sys.argv[1]
 7m = hashlib.sha1()
 8m.update(bytes(data, 'utf-8'))
 9print(m.hexdigest())

这松散地受 启发sha1sum,但它有意处理字符串而不是文件的内容。在 中sha1sum.py,摄取 Python 命令行参数并输出结果的步骤如下:

  • 第 6行将第一个参数的内容存储在data.
  • 第 7 行实例化了一个 SHA1 算法。
  • 第 8 行使用第一个程序参数的内容更新 SHA1 哈希对象。请注意,hash.update它以字节数组为参数,因此需要将data字符串转换为字节数组。
  • 第 9 行打印第 8 行计算的 SHA1 哈希的十六进制表示

当您使用参数运行脚本时,您会得到以下信息:

$ python sha1sum.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565

为了使示例简短,脚本sha1sum.py不处理丢失的 Python 命令行参数。错误处理可以在这个脚本中解决,就像你在reverse_exc.py.

注意:查看hashlib有关 Python 标准库中可用哈希函数的更多详细信息。

sys.argv 文档中,您了解到为了获取 Python 命令行参数的原始字节,您可以使用os.fsencode(). 通过直接从 获取字节sys.argv[1],您不需要执行 的字符串到字节的转换data

 1# sha1sum_bytes.py
 2
 3import os
 4import sys
 5import hashlib
 6
 7data = os.fsencode(sys.argv[1])
 8m = hashlib.sha1()
 9m.update(data)
10print(m.hexdigest())

sha1sum.py和之间的主要区别在sha1sum_bytes.py以下几行中突出显示:

  • 第 7 行填充data了传递给 Python 命令行参数的原始字节。
  • 第 9 行data作为参数传递给m.update(),它接收一个类似字节的对象

执行sha1sum_bytes.py比较输出:

$ python sha1sum_bytes.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565

SHA1 散列的十六进制值与前一个sha1sum.py示例中的相同。

Python 命令行参数剖析

既然您已经探索了 Python 命令行参数的几个方面,最值得注意的是sys.argv,您将应用开发人员在实现命令行界面时经常使用的一些标准。

Python 命令行参数是命令行界面的一个子集。它们可以由不同类型的参数组成:

  1. 选项修改特定命令或程序的行为。
  2. 参数表示要处理的源或目标。
  3. 子命令允许程序使用一组相应的选项和参数定义多个命令。

在深入研究不同类型的参数之前,您将大致了解指导命令行界面和参数设计的公认标准。自1960 年代中期计算机终端出现以来,这些功能已经得到改进。

标准

一些可用的标准提供了一些定义和指南,以促进实现命令及其参数的一致性。这些是主要的 UNIX 标准和参考:

上述标准定义了与程序和 Python 命令行参数相关的任何内容的指南和命名法。以下几点是从这些参考资料中获取的示例:

  • POSIX :
    • 程序或实用程序后跟选项、选项参数和操作数。
    • 所有选项都应以连字符或减号 ( -) 分隔符开头。
    • 选项参数不应该是可选的。
  • GNU :
    • 所有程序都应支持两个标准选项,即--version--help
    • 长命名选项等效于单字母 Unix 样式选项。一个例子是--debug-d
  • 医生
    • 短选项可以堆叠,这意味着-abc相当于-a -b -c.
    • 长选项可以在空格或等号 ( =)后指定参数。long 选项--input=ARG等效于--input ARG.

这些标准定义了在描述命令时有用的符号。当您使用选项-h或调用特定命令时,可以使用类似的表示法来显示它的用法--help

GNU 标准与 POSIX 标准非常相似,但提供了一些修改和扩展。值得注意的是,他们添加了长选项,这是一个以两个连字符 ( --)为前缀的全命名选项。例如,要显示帮助,常规选项为-h,长选项为--help

注意:您不需要严格遵循这些标准。相反,应遵循自 UNIX 出现以来已成功使用多年的约定。如果您为您或您的团队编写一组实用程序,请确保您在不同的实用程序中保持一致

在以下部分中,您将了解有关每个命令行组件、选项、参数和子命令的更多信息。

选项

一个选项,有时被称为一个标志或一个开关,用于修改程序的行为。例如,lsLinux 上的命令列出给定目录的内容。不带任何参数,它列出当前目录中的文件和目录:

$ cd /dev
$ ls
autofs
block
bsg
btrfs-control
bus
char
console

让我们添加几个选项。您可以将-l-s合并为-ls,这会更改终端中显示的信息:

$ cd /dev
$ ls -ls
total 0
0 crw-r--r--  1 root root       10,   235 Jul 14 08:10 autofs
0 drwxr-xr-x  2 root root             260 Jul 14 08:10 block
0 drwxr-xr-x  2 root root              60 Jul 14 08:10 bsg
0 crw-------  1 root root       10,   234 Jul 14 08:10 btrfs-control
0 drwxr-xr-x  3 root root              60 Jul 14 08:10 bus
0 drwxr-xr-x  2 root root            4380 Jul 14 15:08 char
0 crw-------  1 root root        5,     1 Jul 14 08:10 console

一个选项可以接受一个参数,称为option-argument。请参阅od下面的示例:

$ od -t x1z -N 16 main
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  >.ELF............<
0000020

od代表八进制转储。此实用程序以不同的可打印表示形式显示数据,例如八进制(默认值)、十六进制、十进制和 ASCII。在上面的示例中,它采用二进制文件main并以十六进制格式显示文件的前 16 个字节。该选项-t需要一个类型作为选项参数,并-N需要输入字节数。

在上面的例子中,-t给出了 type x1,它代表十六进制和每个整数一个字节。然后z在输入行的末尾显示可打印的字符。-N16作为一个选择参数的,用于限制输入的字节数为16。

参数

参数也被称为操作数参数的POSIX标准。参数表示命令所作用的数据的来源或目的地。例如,cp用于将一个或多个文件复制到文件或目录的命令,至少需要一个源和一个目标:

 1$ ls main
 2main
 3
 4$ cp main main2
 5
 6$ ls -lt
 7main
 8main2
 9...

在第 4 行,cp接受两个参数:

  1. main:源文件
  2. main2:目标文件

然后它将 的内容复制main到名为 的新文件中main2。这两个mainmain2有观点,或操作数,程序的cp

子命令

POSIX 或 GNU 标准中没有记录子命令的概念,但它确实出现在docopt 中。标准的 Unix 实用程序是遵循Unix 哲学的小工具。Unix 程序旨在成为只做一件事并把它做好的程序。这意味着不需要子命令。

相比之下,新一代的方案,其中包括gitgodocker,和gcloud,并配备了一个稍微不同的范式拥抱子命令。它们不一定是 Unix 环境的一部分,因为它们跨越多个操作系统,并且它们部署在需要多个命令的完整生态系统中。

git作为一个例子。它处理多个命令,每个命令可能都有自己的一组选项、选项参数和参数。以下示例适用于 git 子命令branch

  • git branch 显示本地 git 存储库的分支。
  • git branch custom_pythoncustom_python在本地存储库中创建本地分支。
  • git branch -d custom_python删除本地分支custom_python
  • git branch --help显示git branch子命令的帮助。

在 Python 生态系统中,pip也有子命令的概念。一些pip子命令包括listinstallfreeze,或uninstall

视窗

在 Windows 上,有关 Python 命令行参数的约定略有不同,特别是有关命令行选项的约定。要验证这种差异,请使用tasklist,它是一个本机 Windows 可执行文件,显示当前正在运行的进程的列表。它类似于ps在 Linux 或 macOS 系统上。以下是如何tasklist在 Windows 上的命令提示符中执行的示例:

C:/>tasklist /FI "IMAGENAME eq notepad.exe"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
notepad.exe                  13104 Console                    6     13,548 K
notepad.exe                   6584 Console                    6     13,696 K

请注意,选项的分隔符是正斜杠 ( /) 而不是-像 Unix 系统约定那样的连字符 ( )。为便于阅读,程序名称taskslist、 和选项之间有一个空格/FI,但键入 也是正确的taskslist/FI

上面的特定示例tasklist使用过滤器执行以仅显示当前正在运行的记事本进程。可以看到系统有两个正在运行的记事本进程实例。虽然不是等价的,但这类似于在类 Unix 系统的终端中执行以下命令:

$ ps -ef | grep vi | grep -v grep
andre     2117     4  0 13:33 tty1     00:00:00 vi .gitignore
andre     2163  2134  0 13:34 tty3     00:00:00 vi main.c

ps上面的命令显示了所有当前正在运行的vi进程。该行为与Unix Philosophy一致,因为 的输出ps由两个grep过滤器转换。第一个grep命令选择所有出现的vi,第二个命令grep过滤掉grep它自己的出现。

随着 Unix 工具在 Windows 生态系统中的出现,非 Windows 特定的约定也被 Windows 接受。

视觉效果

在 Python 进程开始时,Python 命令行参数分为两类:

  1. Python 选项:这些会影响 Python 解释器的执行。例如,添加选项-O是通过删除assert__debug__语句来优化 Python 程序执行的一种手段。命令行还有其他可用的Python 选项

  2. Python 程序及其参数:在 Python 选项(如果有)之后,您将找到 Python 程序,它是一个文件名,通常具有扩展名.py及其参数。按照惯例,这些也可以由选项和参数组成。

使用以下旨在执行程序的命令,该命令main.py带有选项和参数。请注意,在此示例中,Python 解释器还采用了一些选项,它们是-B-v

$ python -B -v main.py --verbose --debug un deux

在上面的命令行中,选项是 Python 命令行参数,组织如下:

  • 该选项-B告诉 Python 不要.pyc在导入源模块时写入文件。有关.pyc文件的更多详细信息,请查看编译器做什么?您的 CPython 源代码指南中
  • 该选项-v代表详细并告诉 Python 跟踪所有导入语句。
  • 传递给的参数main.py是虚构的,代表两个长选项(--verbose--debug)和两个参数(undeux)。

这个 Python 命令行参数示例可以用图形方式说明如下:

Python 命令行参数剖析

在 Python 程序中main.py,您只能访问由 Python 插入的 Python 命令行参数sys.argv。Python 选项可能会影响程序的行为,但在main.py.

解析 Python 命令行参数的几种方法

现在您将探索一些理解选项、选项参数和操作数的方法。这是通过解析Python 命令行参数来完成的。在本节中,您将看到 Python 命令行参数的一些具体方面以及处理它们的技术。首先,您将看到一个示例,该示例介绍了一种直接的方法,该方法依赖于列表推导来收集选项和参数并将其分离。然后你会:

  • 使用正则表达式提取命令行元素
  • 了解如何处理在命令行传递的文件
  • 以与 Unix 工具兼容的方式理解标准输入
  • 区分程序的从错误中的常规输出
  • 实现自定义解析器以读取 Python 命令行参数

这将为涉及标准库或外部库中的模块的选项做准备,您将在本教程后面了解这些模块。

对于不复杂的事情,以下不强制排序且不处理选项参数的模式可能就足够了:

# cul.py

import sys

opts = [opt for opt in sys.argv[1:] if opt.startswith("-")]
args = [arg for arg in sys.argv[1:] if not arg.startswith("-")]

if "-c" in opts:
    print(" ".join(arg.capitalize() for arg in args))
elif "-u" in opts:
    print(" ".join(arg.upper() for arg in args))
elif "-l" in opts:
    print(" ".join(arg.lower() for arg in args))
else:
    raise SystemExit(f"Usage: {sys.argv[0]} (-c | -u | -l) <arguments>...")

上面程序的目的是修改 Python 命令行参数的大小写。提供三个选项:

  • -c 将参数大写
  • -u 将参数转换为大写
  • -l 将参数转换为小写

代码使用列表推导收集和分离不同的参数类型:

  • 第 5 行通过过滤以连字符 ( )开头的任何 Python 命令行参数来收集所有选项-
  • 第 6 行通过过滤掉选项来组装程序参数

当您使用一组选项和参数执行上面的 Python 程序时,您会得到以下输出:

$ python cul.py -c un deux trois
Un Deux Trois

这种方法在许多情况下可能就足够了,但在以下情况下会失败:

  • 如果顺序很重要,特别是如果选项应该出现在参数之前
  • 如果需要支持选项参数
  • 如果某些参数以连字符 ( -)为前缀

在使用argparse或 之类的库之前,您可以利用其他选项click

常用表达

您可以使用正则表达式来强制执行特定顺序、特定选项和选项参数,甚至是参数类型。为了说明使用正则表达式解析 Python 命令行参数,您将实现 的 Python 版本seq,这是一个打印数字序列的程序。遵循 docopt 约定,规范seq.py可能是这样的:

Print integers from <first> to <last>, in steps of <increment>.

Usage:
  python seq.py --help
  python seq.py [-s SEPARATOR] <last>
  python seq.py [-s SEPARATOR] <first> <last>
  python seq.py [-s SEPARATOR] <first> <increment> <last>

Mandatory arguments to long options are mandatory for short options too.
  -s, --separator=STRING use STRING to separate numbers (default: \n)
      --help             display this help and exit

If <first> or <increment> are omitted, they default to 1. When <first> is
larger than <last>, <increment>, if not set, defaults to -1.
The sequence of numbers ends when the sum of the current number and
<increment> reaches the limit imposed by <last>.

首先,查看一个旨在满足上述要求的正则表达式:

 1args_pattern = re.compile(
 2    r"""
 3    ^
 4    (
 5        (--(?P<HELP>help).*)|
 6        ((?:-s|--separator)\s(?P<SEP>.*?)\s)?
 7        ((?P<OP1>-?\d+))(\s(?P<OP2>-?\d+))?(\s(?P<OP3>-?\d+))?
 8    )
 9    $
10""",
11    re.VERBOSE,
12)

要试验上面的正则表达式,您可以使用正则表达式 101上记录的代码段。正则表达式捕获并强制执行为 给出的要求的几个方面seq。特别是,该命令可能需要:

  1. 一个帮助选项,在短(-h)或长格式(--help),捕获为命名组名为HELP
  2. 分隔符选项,-s--separator,采用可选参数,并作为命名组捕获SEP
  3. 多达三个整数操作数,分别捕获为OP1OP2OP3

为清楚起见,args_pattern上面的模式使用第re.VERBOSE11 行的标志。这允许您将正则表达式扩展到几行以增强可读性。该模式验证以下内容:

  • 参数顺序:选项和参数应按给定的顺序排列。例如,在参数之前需要选项。
  • 选项值**:只有--help-s--separator预期作为选项。
  • 参数互斥:该选项--help与其他选项或参数不兼容。
  • 参数类型:操作数应为正整数或负整数。

为了让正则表达式能够处理这些事情,它需要在一个字符串中查看所有 Python 命令行参数。您可以使用str.join()收集它们:

arg_line = " ".join(sys.argv[1:])

这将生成arg_line一个字符串,其中包含除程序名称之外的所有参数,并以空格分隔。

鉴于上述模式args_pattern,您可以使用以下函数提取 Python 命令行参数:

def parse(arg_line: str) -> Dict[str, str]:
    args: Dict[str, str] = {}
    if match_object := args_pattern.match(arg_line):
        args = {k: v for k, v in match_object.groupdict().items()
                if v is not None}
    return args

该模式已经在处理参数的顺序、选项和参数之间的互斥性以及参数的类型。parse()应用于re.match()参数行以提取正确的值并将数据存储在字典中。

词典包括每个组的名称作为键和它们各自的值。例如,如果arg_line值为--help,则字典为{'HELP': 'help'}。如果arg_line-s T 10,则字典变为{'SEP': 'T', 'OP1': '10'}。您可以展开下面的代码块以查看seqwith 正则表达式的实现。

使用正则表达式实现 seq显示隐藏

下面的代码seq使用正则表达式实现了有限版本的来处理命令行解析和验证:

# seq_regex.py

from typing import List, Dict
import re
import sys

USAGE = (
    f"Usage: {sys.argv[0]} [-s <separator>] [first [increment]] last"
)

args_pattern = re.compile(
    r"""
    ^
    (
        (--(?P<HELP>help).*)|
        ((?:-s|--separator)\s(?P<SEP>.*?)\s)?
        ((?P<OP1>-?\d+))(\s(?P<OP2>-?\d+))?(\s(?P<OP3>-?\d+))?
    )
    $
""",
    re.VERBOSE,
)

def parse(arg_line: str) -> Dict[str, str]:
    args: Dict[str, str] = {}
    if match_object := args_pattern.match(arg_line):
        args = {k: v for k, v in match_object.groupdict().items()
                if v is not None}
    return args

def seq(operands: List[int], sep: str = "\n") -> str:
    first, increment, last = 1, 1, 1
    if len(operands) == 1:
        last = operands[0]
    if len(operands) == 2:
        first, last = operands
        if first > last:
            increment = -1
    if len(operands) == 3:
        first, increment, last = operands
    last = last + 1 if increment > 0 else last - 1
    return sep.join(str(i) for i in range(first, last, increment))

def main() -> None:
    args = parse(" ".join(sys.argv[1:]))
    if not args:
        raise SystemExit(USAGE)
    if args.get("HELP"):
        print(USAGE)
        return
    operands = [int(v) for k, v in args.items() if k.startswith("OP")]
    sep = args.get("SEP", "\n")
    print(seq(operands, sep))

if __name__ == "__main__":
    main()

您可以通过运行以下命令来执行上面的代码:

$ python seq_regex.py 3

这应该输出以下内容:

1
2
3

Try this command with other combinations, including the --help option.

You didn’t see a version option supplied here. This was done intentionally to reduce the length of the example. You may consider adding the version option as an extended exercise. As a hint, you could modify the regular expression by replacing the line (--(?P<HELP>help).*)| with (--(?P<HELP>help).*)|(--(?P<VER>version).*)|. An additional if block would also be needed in main().

至此,您知道了几种从命令行提取选项和参数的方法。到目前为止,Python 命令行参数只是字符串或整数。接下来,您将学习如何处理作为参数传递的文件。

文件处理

现在是时候试验 Python 命令行参数了,这些参数应该是文件名。修改sha1sum.py以将一个或多个文件作为参数处理。您最终会得到原始sha1sum实用程序的降级版本,它将一个或多个文件作为参数并显示每个文件的十六进制 SHA1 哈希值,后跟文件名:

# sha1sum_file.py

import hashlib
import sys

def sha1sum(filename: str) -> str:
    hash = hashlib.sha1()
    with open(filename, mode="rb") as f:
        hash.update(f.read())
    return hash.hexdigest()

for arg in sys.argv[1:]:
    print(f"{sha1sum(arg)}  {arg}")

sha1sum()应用于从您在命令行传递的每个文件中读取的数据,而不是字符串本身。请注意,m.update()它将一个类似字节的对象作为参数,并且在read()使用 mode 打开文件后调用的结果rb将返回一个bytesobject。有关处理文件内容的更多信息,请查看在 Python 中读取和写入文件,特别是使用字节部分。

sha1sum_file.py从在命令行处理字符串到操作文件内容的演变使您更接近于原始实现sha1sum

$ sha1sum main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d  main
125a0f900ff6f164752600550879cbfabb098bc3  main.c

使用相同的 Python 命令行参数执行 Python 程序给出了这个:

$ python sha1sum_file.py main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d  main
125a0f900ff6f164752600550879cbfabb098bc3  main.c

因为您与 shell 解释器或 Windows 命令提示符交互,您还可以从 shell 提供的通配符扩展中受益。为了证明这一点,您可以重用main.py,它显示每个参数以及参数编号及其值:

$ python main.py main.*
Arguments count: 5
Argument      0: main.py
Argument      1: main.c
Argument      2: main.exe
Argument      3: main.obj
Argument      4: main.py

您可以看到 shell 自动执行通配符扩展,以便任何基本名称匹配的文件main,无论扩展名如何,都是sys.argv.

通配符扩展在 Windows 上不可用。要获得相同的行为,您需要在代码中实现它。要重构main.py以使用通配符扩展,您可以使用glob. 下面的示例适用于 Windows,虽然它不像原来的那么简洁main.py,但相同的代码在跨平台的行为相似:

 1# main_win.py
 2
 3import sys
 4import glob
 5import itertools
 6from typing import List
 7
 8def expand_args(args: List[str]) -> List[str]:
 9    arguments = args[:1]
10    glob_args = [glob.glob(arg) for arg in args[1:]]
11    arguments += itertools.chain.from_iterable(glob_args)
12    return arguments
13
14if __name__ == "__main__":
15    args = expand_args(sys.argv)
16    print(f"Arguments count: {len(args)}")
17    for i, arg in enumerate(args):
18        print(f"Argument {i:>6}: {arg}")

main_win.py,expand_args依赖于glob.glob()处理 shell 风格的通配符。您可以在 Windows 和任何其他操作系统上验证结果:

C:/>python main_win.py main.*
Arguments count: 5
Argument      0: main_win.py
Argument      1: main.c
Argument      2: main.exe
Argument      3: main.obj
Argument      4: main.py

这解决了使用星号 ( *) 或问号 ( ?)等通配符处理文件的问题,但是stdin呢?

如果您不向原始sha1sum实用程序传递任何参数,则它希望从标准输入读取数据。这是你在终端输入结束时,你输入的文字Ctrl+D在类Unix系统或Ctrl+Z在Windows上。这些控制序列向终端发送文件结尾 (EOF),终端停止读取stdin并返回输入的数据。

在下一部分中,您将向您的代码添加从标准输入流中读取的功能。

标准输入

当您修改以前的 Python 实现sha1sum以使用 处理标准输入时sys.stdin,您将更接近原始的sha1sum

# sha1sum_stdin.py

from typing import List
import hashlib
import pathlib
import sys

def process_file(filename: str) -> bytes:
    return pathlib.Path(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(data: bytes) -> str:
    sha1_hash = hashlib.sha1()
    sha1_hash.update(data)
    return sha1_hash.hexdigest()

def output_sha1sum(data: bytes, filename: str = "-") -> None:
    print(f"{sha1sum(data)}  {filename}")

def main(args: List[str]) -> None:
    if not args:
        args = ["-"]
    for arg in args:
        if arg == "-":
            output_sha1sum(process_stdin(), "-")
        else:
            output_sha1sum(process_file(arg), arg)

if __name__ == "__main__":
    main(sys.argv[1:])

两个约定适用于这个新sha1sum版本:

  1. 没有任何参数,程序期望在标准输入中提供数据,sys.stdin,这是一个可读的文件对象。
  2. -在命令行中提供连字符 ( ) 作为文件参数时,程序会将其解释为从标准输入读取文件。

试试这个没有任何参数的新脚本。输入的第一格言的Python的禅,然后完成使用键盘快捷键进入Ctrl+D在类Unix系统或Ctrl+Z在Windows上:

$ python sha1sum_stdin.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  -

您还可以包含stdin与其他文件参数混合的参数之一,如下所示:

$ python sha1sum_stdin.py main.py - main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4  main.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  -
125a0f900ff6f164752600550879cbfabb098bc3  main.c

在类 Unix 系统上的另一种方法是提供/dev/stdin而不是-处理标准输入:

$ python sha1sum_stdin.py main.py /dev/stdin main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4  main.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  /dev/stdin
125a0f900ff6f164752600550879cbfabb098bc3  main.c

在 Windows 上没有等效于/dev/stdin,因此-用作文件参数按预期工作。

该脚本sha1sum_stdin.py并未涵盖所有必要的错误处理,但您将在本教程的后面部分介绍一些缺失的功能。

标准输出和标准误差

命令行处理可能与stdin遵守上一节中详述的约定有直接关系。标准输出虽然不是直接相关的,但如果您想遵守Unix Philosophy,它仍然是一个问题。要允许组合小程序,您可能必须考虑三个标准流:

  1. stdin
  2. stdout
  3. stderr

一个程序的输出成为另一个程序的输入,允许您链接小型实用程序。例如,如果您想对 Python 之禅的格言进行排序,那么您可以执行以下操作:

$ python -c "import this" | sort
Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch.
...

为了更好的可读性,上面的输出被截断了。现在假设您有一个程序输出相同的数据但也打印一些调试信息:

# zen_sort_debug.py

print("DEBUG >>> About to print the Zen of Python")
import this
print("DEBUG >>> Done printing the Zen of Python")

执行上面的 Python 脚本给出:

$ python zen_sort_debug.py
DEBUG >>> About to print the Zen of Python
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
...
DEBUG >>> Done printing the Zen of Python

省略号 ( ...) 表示输出被截断以提高可读性。

现在,如果要对格言列表进行排序,则执行如下命令:

$ python zen_sort_debug.py | sort

Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch.
Beautiful is better than ugly.
Complex is better than complicated.
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python
Errors should never pass silently.
...

您可能会意识到您不打算将调试输出作为sort命令的输入。为了解决这个问题,您希望将跟踪发送到标准错误流stderr,而不是:

# zen_sort_stderr.py
import sys

print("DEBUG >>> About to print the Zen of Python", file=sys.stderr)
import this
print("DEBUG >>> Done printing the Zen of Python", file=sys.stderr)

执行zen_sort_stderr.py以观察以下内容:

$ python zen_sort_stderr.py | sort
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python

Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch
....

现在,跟踪显示到终端,但它们不用作sort命令的输入。

自定义解析器

seq如果参数不太复杂,您可以依靠正则表达式来实现。然而,正则表达式模式可能很快使脚本的维护变得困难。在您尝试从特定库获取帮助之前,另一种方法是创建自定义解析器。解析器是一个循环,它一个接一个地获取每个参数,并根据程序的语义应用自定义逻辑。

处理参数的seq_parse.py可能实现如下:

 1def parse(args: List[str]) -> Tuple[str, List[int]]:
 2    arguments = collections.deque(args)
 3    separator = "\n"
 4    operands: List[int] = []
 5    while arguments:
 6        arg = arguments.popleft()
 7        if not operands:
 8            if arg == "--help":
 9                print(USAGE)
10                sys.exit(0)
11            if arg in ("-s", "--separator"):
12                separator = arguments.popleft()
13                continue
14        try:
15            operands.append(int(arg))
16        except ValueError:
17            raise SystemExit(USAGE)
18        if len(operands) > 3:
19            raise SystemExit(USAGE)
20
21    return separator, operands

parse()给出没有 Python 文件名的参数列表,并使用collections.deque()来获得 的好处.popleft(),它从集合的左侧删除元素。随着参数列表的项目展开,您将应用程序预期的逻辑。在parse()您可以观察到以下内容:

  • while循环是在功能的核心,并终止时,有没有更多的参数解析,当调用帮助,或发生错误时。
  • 如果separator检测到该选项,则下一个参数应该是分隔符。
  • operands存储用于计算序列的整数。应该至少有一个操作数,最多三个。

完整版本的代码parse()如下:

单击以展开完整示例。显示隐藏

# seq_parse.py

from typing import Dict, List, Tuple
import collections
import re
import sys

USAGE = (f"Usage: {sys.argv[0]} "
         "[--help] | [-s <sep>] [first [incr]] last")

def seq(operands: List[int], sep: str = "\n") -> str:
    first, increment, last = 1, 1, 1
    if len(operands) == 1:
        last = operands[0]
    if len(operands) == 2:
        first, last = operands
        if first > last:
            increment = -1
    if len(operands) == 3:
        first, increment, last = operands
    last = last + 1 if increment > 0 else last - 1
    return sep.join(str(i) for i in range(first, last, increment))

def parse(args: List[str]) -> Tuple[str, List[int]]:
    arguments = collections.deque(args)
    separator = "\n"
    operands: List[int] = []
    while arguments:
        arg = arguments.popleft()
        if not len(operands):
            if arg == "--help":
                print(USAGE)
                sys.exit(0)
            if arg in ("-s", "--separator"):
                separator = arguments.popleft() if arguments else None
                continue
        try:
            operands.append(int(arg))
        except ValueError:
            raise SystemExit(USAGE)
        if len(operands) > 3:
            raise SystemExit(USAGE)

    return separator, operands

def main() -> None:
    sep, operands = parse(sys.argv[1:])
    if not operands:
        raise SystemExit(USAGE)
    print(seq(operands, sep))

if __name__ == "__main__":
    main()

Note that some error handling aspects are kept to a minimum so as to keep the examples relatively short.

这种解析 Python 命令行参数的手动方法对于一组简单的参数可能就足够了。但是,由于以下原因,当复杂性增加时,它很快就会出错:

  • 大量的参数
  • 参数之间的复杂性和相互依赖
  • 针对参数执行的验证

自定义方法不可重用,需要在每个程序中重新发明轮子。在本教程结束时,您将改进这个手工制作的解决方案并学习一些更好的方法。

验证 Python 命令行参数的几种方法

您已经在几个示例(如seq_regex.py和 )中对 Python 命令行参数执行了验证seq_parse.py。在第一个示例中,您使用了正则表达式,在第二个示例中,使用了自定义解析器。

这两个示例都考虑了相同的方面。他们将预期选项视为短格式 ( -s) 或长格式 ( --separator)。他们考虑了参数的顺序,以便选项不会放在操作数之后。最后,他们考虑了类型、操作数的整数和参数的数量,从一到三个参数。

使用 Python 数据类进行类型验证

以下是尝试验证在命令行传递的参数类型的概念证明。在以下示例中,您验证参数的数量及其各自的类型:

# val_type_dc.py

import dataclasses
import sys
from typing import List, Any

USAGE = f"Usage: python {sys.argv[0]} [--help] | firstname lastname age]"

@dataclasses.dataclass
class Arguments:
    firstname: str
    lastname: str
    age: int = 0

def check_type(obj):
    for field in dataclasses.fields(obj):
        value = getattr(obj, field.name)
        print(
            f"Value: {value}, "
            f"Expected type {field.type} for {field.name}, "
            f"got {type(value)}"
        )
        if type(value) != field.type:
            print("Type Error")
        else:
            print("Type Ok")

def validate(args: List[str]):
    # If passed to the command line, need to convert
    # the optional 3rd argument from string to int
    if len(args) > 2 and args[2].isdigit():
        args[2] = int(args[2])
    try:
        arguments = Arguments(*args)
    except TypeError:
        raise SystemExit(USAGE)
    check_type(arguments)

def main() -> None:
    args = sys.argv[1:]
    if not args:
        raise SystemExit(USAGE)

    if args[0] == "--help":
        print(USAGE)
    else:
        validate(args)

if __name__ == "__main__":
    main()

除非您--help在命令行传递选项,否则此脚本需要两个或三个参数:

  1. 强制字符串: firstname
  2. 强制字符串: lastname
  3. 一个可选的整数: age

因为中的所有项目sys.argv都是字符串,所以如果它由数字组成,您需要将可选的第三个参数转换为整数。str.isdigit()验证字符串中的所有字符是否都是数字。此外,通过使用转换后的参数值构造数据类 Arguments,您可以获得两个验证:

  1. 如果参数的数量与 预期的必填字段的数量不对应Arguments,那么您会收到错误消息。这是最少两个和最多三个字段。
  2. 如果转换后的类型与Arguments数据类定义中定义的类型不匹配,则会出现错误。

您可以通过以下执行看到这一点:

$ python val_type_dc.py Guido "Van Rossum" 25
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van Rossum, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: 25, Expected type <class 'int'> for age, got <class 'int'>
Type Ok

在上面的执行中,参数的数量是正确的,每个参数的类型也是正确的。

现在,执行相同的命令但省略第三个参数:

$ python val_type_dc.py Guido "Van Rossum"
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van Rossum, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: 0, Expected type <class 'int'> for age, got <class 'int'>
Type Ok

结果也是成功的,因为该字段age定义了一个默认值0,所以数据类Arguments不需要它。

相反,如果第三个参数不是正确的类型——比如说,一个字符串而不是整数——那么你会得到一个错误:

python val_type_dc.py Guido Van Rossum
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: Rossum, Expected type <class 'int'> for age, got <class 'str'>
Type Error

预期值Van Rossum, 没有被引号包围,所以它是分开的。姓氏的第二个单词Rossum,是一个作为年龄处理的字符串,应该是一个int. 验证失败。

注意:有关 Python 中数据类使用的更多详细信息,请查看Python 3.7 中数据类终极指南

同样,您也可以使用 aNamedTuple来实现类似的验证。您将使用派生自 的类替换数据类NamedTuplecheck_type()并将更改如下:

from typing import NamedTuple

class Arguments(NamedTuple):
    firstname: str
    lastname: str
    age: int = 0

def check_type(obj):
    for attr, value in obj._asdict().items():
        print(
            f"Value: {value}, "
            f"Expected type {obj.__annotations__[attr]} for {attr}, "
            f"got {type(value)}"
        )
        if type(value) != obj.__annotations__[attr]:
            print("Type Error")
        else:
            print("Type Ok")

ANamedTuple公开_asdict了将对象转换为可用于数据查找的字典之类的函数。它还公开了类似的属性__annotations__,它是一个存储每个字段类型的字典,有关更多信息__annotations__,请查看Python 类型检查(指南)

正如Python 类型检查(指南)中强调的那样,您还可以利用现有的包,如EnforcePydanticPytypes进行高级验证。

自定义验证

与您之前已经探讨过的不同,详细验证可能需要一些自定义方法。例如,如果您尝试sha1sum_stdin.py使用不正确的文件名作为参数执行,则会得到以下结果:

$ python sha1sum_stdin.py bad_file.txt
Traceback (most recent call last):
  File "sha1sum_stdin.py", line 32, in <module>
    main(sys.argv[1:])
  File "sha1sum_stdin.py", line 29, in main
    output_sha1sum(process_file(arg), arg)
  File "sha1sum_stdin.py", line 9, in process_file
    return pathlib.Path(filename).read_bytes()
  File "/usr/lib/python3.8/pathlib.py", line 1222, in read_bytes
    with self.open(mode='rb') as f:
  File "/usr/lib/python3.8/pathlib.py", line 1215, in open
    return io.open(self, mode, buffering, encoding, errors, newline,
  File "/usr/lib/python3.8/pathlib.py", line 1071, in _opener
    return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'bad_file.txt'

bad_file.txt 不存在,但程序试图读取它。

重访main()sha1sum_stdin.py在命令行通过手柄不存在的文件:

 1def main(args):
 2    if not args:
 3        output_sha1sum(process_stdin())
 4    for arg in args:
 5        if arg == "-":
 6            output_sha1sum(process_stdin(), "-")
 7            continue
 8        try:
 9            output_sha1sum(process_file(arg), arg)
10        except FileNotFoundError as err:
11            print(f"{sys.argv[0]}: {arg}: {err.strerror}", file=sys.stderr)

要查看带有此额外验证的完整示例,请展开下面的代码块:

sha1sum_val.py 的完整源代码显示隐藏

# sha1sum_val.py

from typing import List
import hashlib
import pathlib
import sys

def process_file(filename: str) -> bytes:
    return pathlib.Path(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(data: bytes) -> str:
    m = hashlib.sha1()
    m.update(data)
    return m.hexdigest()

def output_sha1sum(data: bytes, filename: str = "-") -> None:
    print(f"{sha1sum(data)}  {filename}")

def main(args: List[str]) -> None:
    if not args:
        output_sha1sum(process_stdin())
    for arg in args:
        if arg == "-":
            output_sha1sum(process_stdin(), "-")
            continue
        try:
            output_sha1sum(process_file(arg), arg)
        except (FileNotFoundError, IsADirectoryError) as err:
            print(f"{sys.argv[0]}: {arg}: {err.strerror}", file=sys.stderr)

if __name__ == "__main__":
    main(sys.argv[1:])

当你执行这个修改过的脚本时,你会得到:

$ python sha1sum_val.py bad_file.txt
sha1sum_val.py: bad_file.txt: No such file or directory

请注意,显示到终端的错误被写入stderr,因此它不会干扰读取 输出的命令所期望的数据sha1sum_val.py

$ python sha1sum_val.py bad_file.txt main.py | cut -d " " -f 1
sha1sum_val.py: bad_file.txt: No such file or directory
d84372fc77a90336b6bb7c5e959bcb1b24c608b4

此命令sha1sum_val.pycutto的输出通过管道传输至仅包含第一个字段。您可以看到cut忽略错误消息,因为它只接收发送到 的数据stdout

Python 标准库

尽管您采用不同的方法来处理 Python 命令行参数,但任何复杂的程序最好利用现有的库来处理复杂的命令行界面所需的繁重工作。从 Python 3.7 开始,标准库中有三个命令行解析器:

  1. argparse
  2. getopt
  3. optparse

标准库中推荐使用的模块是argparse. 标准库也公开,optparse但它已被正式弃用,仅在此处提及以供您参考。它argparse在 Python 3.2 中被取代,您不会在本教程中看到它的讨论。

argparse

您将重新访问sha1sum_val.py的最新克隆sha1sum,以介绍 的优点argparse。为此,您将修改main()并添加init_argparse到实例化argparse.ArgumentParser

 1import argparse
 2
 3def init_argparse() -> argparse.ArgumentParser:
 4    parser = argparse.ArgumentParser(
 5        usage="%(prog)s [OPTION] [FILE]...",
 6        description="Print or check SHA1 (160-bit) checksums."
 7    )
 8    parser.add_argument(
 9        "-v", "--version", action="version",
10        version = f"{parser.prog} version 1.0.0"
11    )
12    parser.add_argument('files', nargs='*')
13    return parser
14
15def main() -> None:
16    parser = init_argparse()
17    args = parser.parse_args()
18    if not args.files:
19        output_sha1sum(process_stdin())
20    for file in args.files:
21        if file == "-":
22            output_sha1sum(process_stdin(), "-")
23            continue
24        try:
25            output_sha1sum(process_file(file), file)
26        except (FileNotFoundError, IsADirectoryError) as err:
27            print(f"{sys.argv[0]}: {file}: {err.strerror}", file=sys.stderr)

与之前的实现相比,由于多出几行代码,您可以获得一种干净的添加方法--help--version以前不存在的选项。预期的参数(要处理的文件)都在filesobject字段中可用argparse.Namespace。该对象通过调用在第 17 行填充parse_args()

要查看具有上述修改的完整脚本,请展开下面的代码块:

sha1sum_argparse.py 的完整源代码显示隐藏

# sha1sum_argparse.py

import argparse
import hashlib
import pathlib
import sys

def process_file(filename: str) -> bytes:
    return pathlib.Path(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(data: bytes) -> str:
    sha1_hash = hashlib.sha1()
    sha1_hash.update(data)
    return sha1_hash.hexdigest()

def output_sha1sum(data: bytes, filename: str = "-") -> None:
    print(f"{sha1sum(data)}  {filename}")

def init_argparse() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        usage="%(prog)s [OPTION] [FILE]...",
        description="Print or check SHA1 (160-bit) checksums.",
    )
    parser.add_argument(
        "-v", "--version", action="version",
        version=f"{parser.prog} version 1.0.0"
    )
    parser.add_argument("files", nargs="*")
    return parser

def main() -> None:
    parser = init_argparse()
    args = parser.parse_args()
    if not args.files:
        output_sha1sum(process_stdin())
    for file in args.files:
        if file == "-":
            output_sha1sum(process_stdin(), "-")
            continue
        try:
            output_sha1sum(process_file(file), file)
        except (FileNotFoundError, IsADirectoryError) as err:
            print(f"{parser.prog}: {file}: {err.strerror}", file=sys.stderr)

if __name__ == "__main__":
    main()

为了说明您通过引入argparse此程序获得的直接好处,请执行以下操作:

$ python sha1sum_argparse.py --help
usage: sha1sum_argparse.py [OPTION] [FILE]...

Print or check SHA1 (160-bit) checksums.

positional arguments:
  files

optional arguments:
  -h, --help     show this help message and exit
  -v, --version  show program's version number and exit

要深入了解 的详细信息argparse,请查看如何使用 argparse 在 Python 中构建命令行接口

getopt

getoptgetoptC 函数中找到它的起源。它有助于解析命令行和处理选项、选项参数和参数。重温parse来自seq_parse.py于使用getopt

def parse():
    options, arguments = getopt.getopt(
        sys.argv[1:],                      # Arguments
        'vhs:',                            # Short option definitions
        ["version", "help", "separator="]) # Long option definitions
    separator = "\n"
    for o, a in options:
        if o in ("-v", "--version"):
            print(VERSION)
            sys.exit()
        if o in ("-h", "--help"):
            print(USAGE)
            sys.exit()
        if o in ("-s", "--separator"):
            separator = a
    if not arguments or len(arguments) > 3:
        raise SystemExit(USAGE)
    try:
        operands = [int(arg) for arg in arguments]
    except ValueError:
        raise SystemExit(USAGE)
    return separator, operands

getopt.getopt() 采用以下参数:

  1. 通常的参数列表减去脚本名称, sys.argv[1:]
  2. 定义短选项的字符串
  3. 长选项的字符串列表

请注意,后跟冒号 ( :)的短选项需要一个选项参数,而尾随等号 ( =)的长选项需要一个选项参数。

的其余代码与下面的折叠代码块中的代码seq_getopt.py相同seq_parse.py并且可用:

seq_getopt.py 的完整源代码显示隐藏

# seq_getopt.py

from typing import List, Tuple
import getopt
import sys

USAGE = f"Usage: python {sys.argv[0]} [--help] | [-s <sep>] [first [incr]] last"
VERSION = f"{sys.argv[0]} version 1.0.0"

def seq(operands: List[int], sep: str = "\n") -> str:
    first, increment, last = 1, 1, 1
    if len(operands) == 1:
        last = operands[0]
    elif len(operands) == 2:
        first, last = operands
        if first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    last = last - 1 if first > last else last + 1
    return sep.join(str(i) for i in range(first, last, increment))

def parse(args: List[str]) -> Tuple[str, List[int]]:
    options, arguments = getopt.getopt(
        args,                              # Arguments
        'vhs:',                            # Short option definitions
        ["version", "help", "separator="]) # Long option definitions
    separator = "\n"
    for o, a in options:
        if o in ("-v", "--version"):
            print(VERSION)
            sys.exit()
        if o in ("-h", "--help"):
            print(USAGE)
            sys.exit()
        if o in ("-s", "--separator"):
            separator = a
    if not arguments or len(arguments) > 3:
        raise SystemExit(USAGE)
    try:
        operands = [int(arg) for arg in arguments]
    except:
        raise SystemExit(USAGE)
    return separator, operands

def main() -> None:
    args = sys.argv[1:]
    if not args:
        raise SystemExit(USAGE)
    sep, operands = parse(args)
    print(seq(operands, sep))

if __name__ == "__main__":
    main()

接下来,您将了解一些可帮助您解析 Python 命令行参数的外部包。

一些外部 Python 包

基于您在本教程中看到的现有约定,Python 包索引 (PyPI)上有一些可用的库,它们采取更多步骤来促进命令行界面的实现和维护。

以下部分简要介绍了ClickPython Prompt Toolkit。您只会接触到这些包的非常有限的功能,因为它们都需要完整的教程(如果不是整个系列)才能正确使用!

点击

在撰写本文时,Click可能是为 Python 程序构建复杂命令行界面的最先进的库。它被多个 Python 产品使用,最著名的是FlaskBlack。在尝试以下示例之前,您需要在Python 虚拟环境或本地环境中安装 Click 。如果您不熟悉虚拟环境的概念,请查看Python 虚拟环境:入门

要安装 Click,请按以下步骤操作:

$ python -m pip install click

那么,Click 如何帮助您处理 Python 命令行参数?这是seq使用 Click的程序的变体:

# seq_click.py

import click

@click.command(context_settings=dict(ignore_unknown_options=True))
@click.option("--separator", "-s",
              default="\n",
              help="Text used to separate numbers (default: \\n)")
@click.version_option(version="1.0.0")
@click.argument("operands", type=click.INT, nargs=-1)
def seq(operands, separator) -> str:
    first, increment, last = 1, 1, 1
    if len(operands) == 1:
        last = operands[0]
    elif len(operands) == 2:
        first, last = operands
        if first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    else:
        raise click.BadParameter("Invalid number of arguments")
    last = last - 1 if first > last else last + 1
    print(separator.join(str(i) for i in range(first, last, increment)))

if __name__ == "__main__":
    seq()

设置ignore_unknown_optionsTrue确保 Click 不会将否定参数解析为选项。负整数是有效的seq参数。

正如您可能已经观察到的那样,您可以免费获得很多!一些精心设计的装饰器足以掩埋样板代码,让您专注于主要代码,这就是seq()本示例中的内容。

注意:有关 Python 装饰器的更多信息,请查看 Python 装饰器入门

剩下的唯一导入是click. 装饰主命令的声明性方法seq()消除了原本需要的重复代码。这可能是以下任何一种:

  • 定义帮助或使用过程
  • 处理程序的版本
  • 捕获设置选项的默认值
  • 验证参数,包括类型

新的seq实现几乎没有触及表面。Click 提供了许多细节,可以帮助您制作非常专业的命令行界面:

  • 输出着色
  • 提示省略参数
  • 命令和子命令
  • 参数类型验证
  • 回调选项和参数
  • 文件路径验证
  • 进度条

还有许多其他功能。查看使用 Click 编写 Python 命令行工具以查看更多基于 Click 的具体示例。

Python 提示工具包

还有其他流行的 Python 包可以处理命令行界面问题,例如docopt for Python。因此,您可能会发现选择Prompt Toolkit有点违反直觉。

Python提示符工具包提供的功能可让您的命令行应用程序漂移从Unix哲学之遥。然而,它有助于弥合晦涩难懂的命令行界面和成熟的图形用户界面之间的差距。换句话说,它可能有助于使您的工具和程序更加用户友好。

除了像前面的示例一样处理 Python 命令行参数之外,您还可以使用此工具,但这为您提供了一条通往类似 UI 的方法的途径,而无需依赖完整的Python UI 工具包。要使用prompt_toolkit,您需要安装它pip

$ python -m pip install prompt_toolkit

您可能会发现下一个示例有点做作,但其目的是激发想法并使您稍微远离与您在本教程中看到的约定有关的命令行的更严格方面。

正如你已经看到了这个例子的核心逻辑,下面的代码片段只展示了与前面的例子明显不同的代码:

def error_dlg():
    message_dialog(
        title="Error",
        text="Ensure that you enter a number",
    ).run()

def seq_dlg():
    labels = ["FIRST", "INCREMENT", "LAST"]
    operands = []
    while True:
        n = input_dialog(
            title="Sequence",
            text=f"Enter argument {labels[len(operands)]}:",
        ).run()
        if n is None:
            break
        if n.isdigit():
            operands.append(int(n))
        else:
            error_dlg()
        if len(operands) == 3:
            break

    if operands:
        seq(operands)
    else:
        print("Bye")        

actions = {"SEQUENCE": seq_dlg, "HELP": help, "VERSION": version}

def main():
    result = button_dialog(
        title="Sequence",
        text="Select an action:",
        buttons=[
            ("Sequence", "SEQUENCE"),
            ("Help", "HELP"),
            ("Version", "VERSION"),
        ],
    ).run()
    actions.get(result, lambda: print("Unexpected action"))()

上面的代码涉及交互方式并可能引导用户输入预期的输入,并使用三个对话框以交互方式验证输入:

  1. button_dialog
  2. message_dialog
  3. input_dialog

Python Prompt Toolkit 公开了许多其他功能,旨在改善与用户的交互。对处理程序main()的调用是通过调用存储在字典中的函数来触发的。如果您以前从未遇到过这个 Python 习语,请查看在 Python 中模拟 switch/case 语句

您可以prompt_toolkit通过展开下面的代码块来查看该程序的完整示例:

seq_prompt.py 的完整源代码显示隐藏

# seq_prompt.py

import sys
from typing import List
from prompt_toolkit.shortcuts import button_dialog, input_dialog, message_dialog

def version():
    print("Version 1.0.0")

def help():
    print("Print numbers from FIRST to LAST, in steps of INCREMENT.")

def seq(operands: List[int], sep: str = "\n"):
    first, increment, last = 1, 1, 1
    if len(operands) == 1:
        last = operands[0]
    elif len(operands) == 2:
        first, last = operands
        if first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    last = last - 1 if first > last else last + 1
    print(sep.join(str(i) for i in range(first, last, increment)))

def error_dlg():
    message_dialog(
        title="Error",
        text="Ensure that you enter a number",
    ).run()

def seq_dlg():
    labels = ["FIRST", "INCREMENT", "LAST"]
    operands = []
    while True:
        n = input_dialog(
            title="Sequence",
            text=f"Enter argument {labels[len(operands)]}:",
        ).run()
        if n is None:
            break
        if n.isdigit():
            operands.append(int(n))
        else:
            error_dlg()
        if len(operands) == 3:
            break

    if operands:
        seq(operands)
    else:
        print("Bye")        

actions = {"SEQUENCE": seq_dlg, "HELP": help, "VERSION": version}

def main():
    result = button_dialog(
        title="Sequence",
        text="Select an action:",
        buttons=[
            ("Sequence", "SEQUENCE"),
            ("Help", "HELP"),
            ("Version", "VERSION"),
        ],
    ).run()
    actions.get(result, lambda: print("Unexpected action"))()

if __name__ == "__main__":
    main()

当您执行上面的代码时,您会看到一个对话框,提示您采取行动。然后,如果您选择操作Sequence,则会显示另一个对话框。收集所有必要的数据、选项或参数后,对话框消失,结果在命令行打印,如前面的示例所示:

提示工具包示例

随着命令行的发展,您可以看到一些尝试以更有创意的方式与用户交互,PyInquirer等其他包也允许您利用非常交互的方法。

为了进一步探索的世界基于文本的用户界面(TUI) ,检查了大楼控制台用户界面第三方部分引导你Python的打印功能

如果您对研究完全依赖图形用户界面的解决方案感兴趣,那么您可以考虑查看以下资源:

结论

在本教程中,您浏览了 Python 命令行参数的许多不同方面。您应该准备好将以下技能应用到您的代码中:

  • Python 命令行参数的约定和伪标准
  • 起源sys.argv在Python
  • 用法sys.argv运行 Python 程序提供灵活性
  • Python标准库argparsegetopt抽象命令行处理
  • 强大的Python包一样click,并python_toolkit进一步提高了程序的可用性

无论您运行的是小脚本还是复杂的基于文本的应用程序,当您公开命令行界面时,您都将显着改善 Python 软件的用户体验。事实上,您可能就是这些用户之一!

下次使用应用程序时,您会喜欢随--help选项一起提供的文档,或者您可以传递选项和参数而不是修改源代码以提供不同的数据。

其他资源

要进一步了解 Python 命令行参数及其许多方面,您可能需要查看以下资源:

您可能还想尝试针对相同问题的其他 Python 库,同时为您提供不同的解决方案:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。