Python 命令行库的大乱

当你想实现一个命令行程序时,或许第一个想到的是用 Python 来实现。比如 CentOS 上大名鼎鼎的包管理工具 yum 就是基于 Python 实现的。

而 Python 的世界中有很多命令行库,每个库都各具特色。但我们往往不知道其背后的设计理念,也因此在选择时感到迷茫。这些库的作者为何在重复造轮子,他是从哪个角度来考虑,来让命令行库“演变”到一个新的更好用的形态。

为了能够更加直观地感受到命令行库的设计理念,在此之前,我们不妨设计一个名为 calc 的命令行程序,它能:

支持 echo 子命令,对输入的字符串做处理来输出

若不提供任何选项,则输出原始内容
若提供 --lower 选项,则输出小写字符串
若提供 --upper 选项,则输出大写字符串
支持 eval 子命令,针对输入调用 Python 的 eval 函数,将结果输出(作为示例,我们不考虑安全性问题)
argparse
argparse 作为 Python 的标准库,可能会是你想到第一个命令行库。

argparse 的设计理念就是提供给开发者最细粒度的控制。换句话说,你需要告诉它必不可少的细节,比如参数的类型是什么,处理参数的动作是怎样的。

在 argparse 的世界中,需要:

设置解析器,作为后续定义参数和解析命令行的基础。如果要实现子命令,则还要设置子解析器。
定义参数,包括名称、类型、动作、帮助等。其中的动作是指对于此参数的初步处理,是直接存下来,还是作为布尔值,亦或是追加到列表中等等
解析参数
根据参数编写业务逻辑
以下示例是基于 argparse 的 calc 程序:

import argparse

def echo_text(args):
if args.lower:
print(args.text.lower())
elif args.upper:
print(args.text.upper())
else:
print(args.text)

def eval_expression(args):
print(eval(args.expression))

1. 设置解析器

parser = argparse.ArgumentParser(description=‘Calculator Program.’)
subparsers = parser.add_subparsers()

2. 定义参数

2.1 echo 子命令

echo 子解析器

echo_parser = subparsers.add_parser(
‘echo’, help=‘Echo input text in multiple forms’)

添加位置参数 text

echo_parser.add_argument(‘text’, help=‘Input text’)

–lower/–upper 互斥,需要设置互斥组

echo_group = echo_parser.add_mutually_exclusive_group()

添加选项参数 --lower/–upper,这里action的作用就是将之变为布尔变量

echo_parser.add_argument(’–lower’, action=‘store_true’, help=‘Lower input text’)
echo_parser.add_argument(’–upper’, action=‘store_true’, help=‘Upper input text’)

设置此命令的处理函数

echo_parser.set_defaults(handle=echo_text)

eval 子解析器

eval_parser = subparsers.add_parser(
‘eval’, help=‘Eval input expression and return result’)

添加位置参数 expression

eval_parser.add_argument(‘expression’, help=‘Expression to eval’)

设置此命令的处理函数

eval_parser.set_defaults(handle=eval_expression)

3. 解析参数

args = parser.parse_args([‘echo’, ‘–upper’, ‘Hello, World’])
print(args) # 结果:Namespace(lower=True, text=‘Hello, World’, upper=False)

args = parser.parse_args([‘eval’, ‘1+2*3’])

print(args) # 结果:Namespace(expression=‘1+2*3’)

4. 业务逻辑处理

args.handle(args)
从上述示例可以看到,要实现子命令,对应地需要添加子解析器。然后最为关键的就是要定义参数,需要通过 add_argument 很明确地告诉 argparse 参数长什么样,需要怎么处理:

它是位置参数 text/expression,还是选项参数 --lower/–upper
若是选项参数,是否互斥
参数的是存成什么形式,比如 action=‘store_true’ 表示存成布尔
子命令的响应函数
通过 argparse 实现的整个过程是很计算机思维的,且比较冗长。其优点是灵活,所有的功能都涵盖到了;但缺点则是将定义和处理割裂,尤其在程序功能复杂时会愈加凌乱和不直观,难以理解和维护。

docopt
有人喜欢 argparse 这样命令式的写法,就会有人喜欢声明式的写法。而 docopt 恰巧这就是这样一个命令行库。设计它的初衷就是对于熟悉命令行程序帮助信息的开发者来说,直接通过编写帮助信息来描述整个命令行参数定义的元信息会是更加简单快捷的方式。这种声明式的语法描述某种程度上会比过程式地定义参数来的更加简单和直观。

在 docopt 的世界中,需要:

定义接口描述/帮助信息,这一步是它的特色和重点
解析参数,获得一个字典
根据参数编写业务逻辑
以下示例是基于 docopt 的 calc 程序:

1. 定义接口描述/帮助信息

“”"Calculator Program.

Usage:
calc echo [–lower | --upper]
calc eval

Commands:
echo Echo input text in multiple forms
eval Eval input expression and return result

Options:
-h --help Show help
–lower Lower input text
–upper Upper input text
“”"
from docopt import docopt

def echo_text(args):
if args[’–lower’]:
print(args[’’].lower())
elif args[’–upper’]:
print(args[’’].upper())
else:
print(args[’’])

def eval_expression(args):
print(eval(args[’’]))

2. 解析命令行

args = docopt(doc, argv=[‘echo’, ‘–upper’, ‘Hello, World’])

结果:{’–lower’: False, ‘–upper’: True, ‘’: None, ‘’: ‘Hello, World’, ‘echo’: True, ‘eval’: False}

print(args)

3. 业务逻辑

if args[‘echo’]:
echo_text(args)
elif args[‘eval’]:
eval_expression(args)
从上述示例可以看到,我们通过文档字符串 doc 定义了接口描述,这和 argparse 中 一系列参数定义的行为是等价的,然后 docopt 便会根据这个元信息把命令行参数转换为一个字典。业务逻辑中就需要对这个字典进行处理。

相比于 argparse:

对于较为复杂的命令,命令和参数元信息的定义上 docopt 会更加简单
在业务逻辑的处理上,argparse 在一些简单参数的处理上会更加便捷,且命令和处理函数之间可以方便路由(比如示例中的情形);相对来说 docopt 转换为字典后就把所有处理交给业务逻辑的方式会更加复杂
click
不论是 argparse 还是 docopt,元信息的定义和处理都是割裂开的。而命令行程序本质上是定义参数并对参数进行处理,而处理参数的逻辑一定是与所定义的参数有关联的。那可不可以用函数和装饰器来实现处理参数逻辑与定义参数的关联呢?click 正好就是以这种使用方式来设计的。

装饰器这样一个优雅的语法糖是元信息定义和处理逻辑之间的绝妙胶水,从而暗示了两者的路有关系。对比于前两个命令行库的路由实现着实优雅了不少。

在 click 的世界中:

通过装饰器定义命令和参数的元信息
使用此装饰器装饰处理函数
对,就是这么简单。

以下示例是基于 click 的 calc 程序:

import sys
import click

sys.argv = [‘calc’, ‘echo’, ‘–upper’, ‘Hello, World’]

@click.group(help=‘Calculator Program.’)
def cli():
pass

2. 定义参数

@cli.command(name=‘echo’, help=‘Echo input text in multiple forms’)
@click.argument(‘text’)
@click.option(’–lower’, is_flag=True, help=‘Lower input text’)
@click.option(’–upper’, is_flag=True, help=‘Upper input text’)

1. 业务逻辑

def echo_text(text, lower, upper):
if lower:
print(text.lower())
elif upper:
print(text.upper())
else:
print(text)

@cli.command(name=‘eval’, help=‘Eval input expression and return result’)
@click.argument(‘expression’)
def eval_expression(expression):
print(eval(expression))

cli()
从上述示例可以看到,元信息定义和处理逻辑无缝绑定在一起,能够直观地看出对应的参数会如何处理,这个优势在有大量参数需要处理时显得尤为突出。在处理函数中,接收到不再是像 argparse 或 docopt 中的一个包含所有参数的变量,而是具体的参数变量,这让处理逻辑在参数使用上也变得更加简便。

此外,click 还内置了很多实用工具和增强能力,如参数自动补全、分页支持、颜色、进度条等功能,能够有效提升开发效率。

fire
虽然前面三个库已经足够强大,但是仍然会有人认为不够简单。是否还有进一步简化的空间呢?如果只是定义函数,是否能让框架推测出参数元信息呢?理论上还真是可以。

fire 用一种面向广义对象的方式来玩转命令行,这种对象可以是类、函数、字典、列表等,它更加灵活,也更加简单。你都不需要定义参数类型,fire 会根据输入和参数默认值来自动判断,这无疑进一步简化了实现过程。

在 fire 的世界中,定义 Python 对象就够了。

以下示例是基于 fire 的 calc 程序:

import sys
import fire

sys.argv = [‘calc’, ‘echo’, ‘“Hello, World”’, ‘–upper’]

业务逻辑

类中有几个方法,就意味着命令行程序有几个同名命令

class Calc:
# text 没有任何默认值,视为位置参数
# lower/upper 有布尔类型的默认值,视为选项参数 --lower/–upper,
# 且指定了为 True,不指定 False
def echo(self, text, lower=False, upper=False):
“”“Echo input text in multiple forms”""
if lower:
print(text.lower())
elif upper:
print(text.upper())
else:
print(text)

def eval(self, expression):"""Eval input expression and return result"""print(eval(expression))

fire.Fire(Calc)
从上面的示例可以看出,使用 fire 足够的简单,一切都是根据约定来进行推断,包括支持哪些命令,每个命令接受的什么参数和选项。这种方式可以说是足够的 Pythonic,相比于 click,fire 把命令行参数的定义和函数参数的定义融为了一体。通过它,我们真的就只用关注业务逻辑。

不过简单往往也意味着对于复杂需求的捉襟见肘。仅仅通过默认值来推导命令行参数所能表达的情况是有限的,比如互斥选项、位置参数的类型限定都无法通过框架来表达,而只能由业务逻辑去判断。

typer
那么该如何在保持像 fire 这样简单实现的方式下,增强参数元信息的表达能力呢?既然默认参数的能力有限,那么如果使用 Python 3 的类型注解呢?

typer 站在 click 巨人的肩膀上,借助 Python 3 类型注解的特性,既满足了简单直观编写的需要,又达到了应对复杂场景的目的,可谓是现代化的命令行库。

在 typer 的世界中,也是直接编写业务逻辑,和 fire 稍稍不同的点是使用了类型注解和默认值来表达参数元信息定义。

以下示例是基于 typer 的 calc 程序:

import sys
import typer

sys.argv = [‘calc’, ‘echo’, ‘“Hello, World”’, ‘–upper’]
cli = typer.Typer(help=‘Calculator Program.’)

定义命令 echo,及处理函数

text 无默认值,视为位置参数,类型为字符串

lower/upper 类型为 bool,默认值为 False,视为选项 --lower/–upper,

且指定了为 True,不指定 False

@cli.command(name=‘echo’)
def echo_text(text: str, lower: bool = False, upper: bool = False):
“”“Echo input text in multiple forms”""
if lower:
print(text.lower())
elif upper:
print(text.upper())
else:
print(text)

定义命令 eval,及处理函数

expression 无默认值,视为位置参数,类型为字符串

@cli.command(name=‘eval’)
def eval_expression(expression: str):
“”“Eval input expression and return result”""
print(eval(expression))

cli()
从上面的示例可以看出,相比于 click,它免去了参数元信息的繁琐定义,取而代之的是类型注解;相比于 fire,它的元信息定义能力则大大增强,可以通过指定默认值为 typer.Option 或 typer.Argument 来进一步扩展参数和选项的语义。可以说是,typer 达到了简单与灵活的完美平衡。

横向对比
最后,我们横向对比下 argparse、docopt、click、fire、typer 库的各项功能和特点:

argpase docopt click fire typer
使用步骤数 4 步 3 步 2 步 1 步 1 步
使用步骤数 1. 设置解析器
2. 定义参数
3. 解析命令行
4. 业务逻辑 1. 定义接口描述
2. 解析命令行
3. 业务逻辑 1. 业务逻辑
2. 定义参数 1. 业务逻辑 1 . 业务逻辑
选项参数
(如 --sum) ✔ ✔ ✔ ✔ ✔
位置参数
(如 X Y) ✔ ✔ ✔ ✔ ✔
参数默认值 ✔ ✘ ✔ ✔ ✔
互斥选项
(如 --car 和 --bus 只能二选一) ✔ ✔ ✔
可通过第三方库支持 ✘ ✘
可变参数
(如指定多个 --file) ✔ ✔ ✔ ✔ ✔
嵌套/父子命令 ✔ ✔ ✔ ✔ ✔
工具箱 ✘ ✘ ✔ ✔ ✔
链式命令调用 ✘ ✘ ✘ ✔ ✘
类型约束 ✔ ✘ ✔ ✘ ✔
Python 的命令行库种类繁多、各具特色,它们并非是重复造轮子的产物,其背后的思想值得学习。结合横向对比的总结,可以选择出符合使用场景的库。如果几个库都符合,那么就选择你所偏爱的风格。

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/514538.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统初学者指南,观点|Linux 系统调用的初学者指南

在过去的几年中,我一直在做大量容器相关的工作。先前,我看到 Julien Friedman 的一个很棒的演讲,它用几行 Go 语言写了一个容器框架。这让我突然了解到容器只是一个受限的 Linux 进程中的机器。构建这个受限视图涉及到 Golang 系统调用包中的…

我在阿里云做前端代码智能化

作为一个整天以代码为伴的码农,避免不了会接触到各种代码提示工具,但是呢,用久了之后会发现他们都有个共同点,那就是 模型巨大,动辄几百兆;并且模型大必然需要更多的计算,同样会导致电脑内存占用…

英特尔携手百度全方位深化合作 共筑智能生态

2021年7月29日,英特尔公司今日出席智能经济高峰论坛暨百度云智峰会2021并分享了一系列与百度在人工智能、云计算、智能边缘等方面的最新合作进展。在智能技术方面,百度基于第三代英特尔至强可扩展处理器,打造全功能AI开发平台Baidu Machine L…

决策树之 GBDT 算法 - 回归部分

GBDT(Gradient Boosting Decision Tree)是被工业界广泛使用的机器学习算法之一,它既可以解决回归问题,又可以应用在分类场景中,该算法由斯坦福统计学教授 Jerome H. Friedman 在 1999 年发表。本文中,我们主…

广技师17专插本c语言答案,广东技术师范学院2017年专插本C语言程序设计(1)

1、广东技术师范学院2017 年专插本 C语言程序设计注意:请将答案写在答题纸上,否则无效!一、判断题: (12 分每题 2 分)1、 C 语言规定 :在一个源程序中 ,main 函数的位置必须在最开始。2、假设所有变量均为整型,则表达式 (a2,b5,b,…

深度强化学习在时序数据压缩中的应用--ICDE 2020收录论文

彼节者有间,而刀刃者无厚;以无厚入有间,恢恢乎其于游刃必有余地矣 ----- 庖丁解牛 前言:随着移动互联网、IoT、5G等的应用和普及,一步一步地我们走进了数字经济时代。随之而来的海量数据将是一种客观的存在&#xff0…

技术干货 | mPaaS 框架下如何使用 Crash SDK 对闪退进行分析?

简介: Android Native Crash 处理案例分享 目前 mPaaS Android 是使用的是 Crash SDK 对闪退进行的处理,Crash SDK 是 Android 平台上一款功能强大的崩溃日志收集 SDK,有着极高的崩溃收集率和完整、全面的崩溃日志信息,生成的日志…

山石网科蒋东毅:网络连接矩阵复杂化,传统安全防护框架需重构

编辑 | 宋慧 供稿 | 山石网科 头图 | 蒋东毅在 ISC 2021主题论坛发表演讲 7月28日上午,在ISC 2021 第九届互联网安全大会主题论坛上,山石网科高级副总裁、首席战略官(CSO)蒋东毅带来了一场主题为《政企安全面临的多重挑战和未来趋…

如何使用java来实现windows系统关机

可以使用Java代码来调用操作系统的命令行来实现Windows关机操作。具体步骤如下: import java.io.IOException;public class ShutdownWindows {public static void main(String[] args) {try {// 调用命令行执行关机命令Process process Runtime.getRuntime().exec…

设树采用孩子兄弟表示法存放.用类c语言设计算法计算树的高度.,(数据结构课程设计分类题目.doc...

(数据结构课程设计分类题目线性表顺序表:1、设有一元素为整数的线性表L(a1,a2,a3,…,an),存放在一维数组A[N]中,设计一个算法,以表中an作为参考元素,将该表分为左、右两部分,其中左半部分每个元素小于等于an,右半部分每个元素都大于an, an位于分界位置上(要求结果仍…

双11“新贵”红星美凯龙:数据中台充分给足转型马力

今年“天猫双11”全球狂欢季,让双11新手红星美凯龙刷足了存在感。从去年首度试水双11,到拿下今年618天猫超级晚、“天猫双11狂欢夜”两大S级总冠名,红星美凯龙通过线上玩法频频出圈,成为中国企业化数字化转型、数字化用户运营的创新样本。 红星美凯龙董事长车建新指出:红星美凯…

CDN应用进阶 | 正确使用CDN 让你更好规避安全风险

为了帮助用户更好地了解和使用CDN产品,CDN应用实践进阶系统课程开课了。12月17日,阿里云CDN产品专家彭飞在线分享了《正确使用CDN,让你更好规避安全风险》议题,内容主要包括以下几个方面: 使用CDN的常见误区和问题有哪…

“程序员千万不要选全栈开发”

作者 | 千鸟(网名) 小路助手开发者责编 | 晋兆雨出品 | CSDN(ID:CSDNnews)对于大多数人来说,大学毕业后选择一家满意的公司,一路升职加薪才是正解,但他却偏偏选择了一条鲜有人知的…

「直播回顾」Mars:加速数据科学的新方式

简介: 本文从数据科学概念、背景和现状切入,引出加速数据科学的新方式Mars,并介绍了Mars具体能解决的一些问题和背后的逻辑、哲学,同时对Mars整体数据处理流程进行了介绍。 本文分为4个部分: Mars的背景和现状 Mars解…

围观|第一代云原生企业米哈游如何让想象发生?

作者 | 贾宁宇 来源|阿里巴巴云原生公众号 在米哈游的办公区,有一间会议室,专门留给了阿里云工程师。 今年,是这家二次元文化公司创立的第九年,米哈游和阿里云的交情,也有八年了。 米哈游总裁刘伟还记得多年前&…

作为一名通信老司机,我是如何看待翼龙通信无人机救灾的?

作者:小枣君来源:鲜枣课堂昨天,关于翼龙无人机救灾的新闻,刷屏了整个网络。由国家应急管理部紧急调派的翼龙-2H应急救灾型无人机,搭载中国移动的基站设备,从贵州安顺出发,连续出动两次&#xff…

揭秘大流量场景下发布如「丝般顺滑」背后的原因

为什么很多互联网公司不敢在白天发布,都选择在半夜发布。要是能摆脱半夜发布的窘境,它不香吗?选择在半夜发布无非是为了减少对用户的影响,出了问题影响面可控。 那我们就来谈谈,发布会有哪些问题。 若您的应用没有上…

Serverless 落地之痛怎么解?

传统业务在开发上线的过程中,需要团队合作,每个人开发一部分,合并代码,开发联调,然后进行资源评估,测试环境搭建、线上环境搭建、测试上线、运维。但是在 Serverless 时代下,开发者只需要开发自…

可信云十年,重磅研究成果与2021云计算十大关键词悉数发布

编辑 | 宋慧 出品 | CSDN云计算 头图 | 2021可信云大会现场 可信云从提出到发展至今,已经历经了十个年头,可信云大会也已举办到第八届。2021年7月27-28日,由中国信息通信研究院、中国通信标准化协会联合主办的“2021可信云大会”盛大开幕&am…

「直播回顾」Mars应用与最佳实践

简介: 本文首先对Mars的概念、功能、优势进行了介绍,随后,对Mars几个典型的应用场景进行介绍,并通过两个Demo展示了在使用Mars后数据科学性能的提升,最后总结了Mars的最佳实践,让使用Mars更高效便捷。 本文…