日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

Python 命令行库的大乱

發(fā)布時(shí)間:2024/8/23 python 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python 命令行库的大乱 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

當(dāng)你想實(shí)現(xiàn)一個(gè)命令行程序時(shí),或許第一個(gè)想到的是用 Python 來(lái)實(shí)現(xiàn)。比如 CentOS 上大名鼎鼎的包管理工具?yum?就是基于 Python 實(shí)現(xiàn)的。

而 Python 的世界中有很多命令行庫(kù),每個(gè)庫(kù)都各具特色。但我們往往不知道其背后的設(shè)計(jì)理念,也因此在選擇時(shí)感到迷茫。這些庫(kù)的作者為何在重復(fù)造輪子,他是從哪個(gè)角度來(lái)考慮,來(lái)讓命令行庫(kù)“演變”到一個(gè)新的更好用的形態(tài)。

為了能夠更加直觀地感受到命令行庫(kù)的設(shè)計(jì)理念,在此之前,我們不妨設(shè)計(jì)一個(gè)名為?calc?的命令行程序,它能:

支持?echo?子命令,對(duì)輸入的字符串做處理來(lái)輸出

若不提供任何選項(xiàng),則輸出原始內(nèi)容
若提供?--lower?選項(xiàng),則輸出小寫(xiě)字符串
若提供?--upper?選項(xiàng),則輸出大寫(xiě)字符串
支持?eval?子命令,針對(duì)輸入調(diào)用 Python 的?eval?函數(shù),將結(jié)果輸出(作為示例,我們不考慮安全性問(wèn)題)
argparse
argparse?作為 Python 的標(biāo)準(zhǔn)庫(kù),可能會(huì)是你想到第一個(gè)命令行庫(kù)。

argparse?的設(shè)計(jì)理念就是提供給開(kāi)發(fā)者最細(xì)粒度的控制。換句話(huà)說(shuō),你需要告訴它必不可少的細(xì)節(jié),比如參數(shù)的類(lèi)型是什么,處理參數(shù)的動(dòng)作是怎樣的。

在?argparse?的世界中,需要:

設(shè)置解析器,作為后續(xù)定義參數(shù)和解析命令行的基礎(chǔ)。如果要實(shí)現(xiàn)子命令,則還要設(shè)置子解析器。
定義參數(shù),包括名稱(chēng)、類(lèi)型、動(dòng)作、幫助等。其中的動(dòng)作是指對(duì)于此參數(shù)的初步處理,是直接存下來(lái),還是作為布爾值,亦或是追加到列表中等等
解析參數(shù)
根據(jù)參數(shù)編寫(xiě)業(yè)務(wù)邏輯
以下示例是基于?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. 設(shè)置解析器

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

2. 定義參數(shù)

2.1 echo 子命令

echo 子解析器

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

添加位置參數(shù) text

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

–lower/–upper 互斥,需要設(shè)置互斥組

echo_group = echo_parser.add_mutually_exclusive_group()

添加選項(xiàng)參數(shù) --lower/–upper,這里action的作用就是將之變?yōu)椴紶栕兞?/h1>

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

設(shè)置此命令的處理函數(shù)

echo_parser.set_defaults(handle=echo_text)

eval 子解析器

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

添加位置參數(shù) expression

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

設(shè)置此命令的處理函數(shù)

eval_parser.set_defaults(handle=eval_expression)

3. 解析參數(shù)

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

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

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

4. 業(yè)務(wù)邏輯處理

args.handle(args)
從上述示例可以看到,要實(shí)現(xiàn)子命令,對(duì)應(yīng)地需要添加子解析器。然后最為關(guān)鍵的就是要定義參數(shù),需要通過(guò)?add_argument?很明確地告訴?argparse?參數(shù)長(zhǎng)什么樣,需要怎么處理:

它是位置參數(shù)?text/expression,還是選項(xiàng)參數(shù)?--lower/–upper
若是選項(xiàng)參數(shù),是否互斥
參數(shù)的是存成什么形式,比如?action=‘store_true’?表示存成布爾
子命令的響應(yīng)函數(shù)
通過(guò)?argparse?實(shí)現(xiàn)的整個(gè)過(guò)程是很計(jì)算機(jī)思維的,且比較冗長(zhǎng)。其優(yōu)點(diǎn)是靈活,所有的功能都涵蓋到了;但缺點(diǎn)則是將定義和處理割裂,尤其在程序功能復(fù)雜時(shí)會(huì)愈加凌亂和不直觀,難以理解和維護(hù)。

docopt
有人喜歡?argparse?這樣命令式的寫(xiě)法,就會(huì)有人喜歡聲明式的寫(xiě)法。而?docopt?恰巧這就是這樣一個(gè)命令行庫(kù)。設(shè)計(jì)它的初衷就是對(duì)于熟悉命令行程序幫助信息的開(kāi)發(fā)者來(lái)說(shuō),直接通過(guò)編寫(xiě)幫助信息來(lái)描述整個(gè)命令行參數(shù)定義的元信息會(huì)是更加簡(jiǎn)單快捷的方式。這種聲明式的語(yǔ)法描述某種程度上會(huì)比過(guò)程式地定義參數(shù)來(lái)的更加簡(jiǎn)單和直觀。

在?docopt?的世界中,需要:

定義接口描述/幫助信息,這一步是它的特色和重點(diǎn)
解析參數(shù),獲得一個(gè)字典
根據(jù)參數(shù)編寫(xiě)業(yè)務(wù)邏輯
以下示例是基于?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’])

結(jié)果:{’–lower’: False, ‘–upper’: True, ‘’: None, ‘’: ‘Hello, World’, ‘echo’: True, ‘eval’: False}

print(args)

3. 業(yè)務(wù)邏輯

if args[‘echo’]:
echo_text(args)
elif args[‘eval’]:
eval_expression(args)
從上述示例可以看到,我們通過(guò)文檔字符串?doc?定義了接口描述,這和?argparse?中 一系列參數(shù)定義的行為是等價(jià)的,然后?docopt?便會(huì)根據(jù)這個(gè)元信息把命令行參數(shù)轉(zhuǎn)換為一個(gè)字典。業(yè)務(wù)邏輯中就需要對(duì)這個(gè)字典進(jìn)行處理。

相比于?argparse:

對(duì)于較為復(fù)雜的命令,命令和參數(shù)元信息的定義上?docopt?會(huì)更加簡(jiǎn)單
在業(yè)務(wù)邏輯的處理上,argparse?在一些簡(jiǎn)單參數(shù)的處理上會(huì)更加便捷,且命令和處理函數(shù)之間可以方便路由(比如示例中的情形);相對(duì)來(lái)說(shuō)?docopt?轉(zhuǎn)換為字典后就把所有處理交給業(yè)務(wù)邏輯的方式會(huì)更加復(fù)雜
click
不論是?argparse?還是?docopt,元信息的定義和處理都是割裂開(kāi)的。而命令行程序本質(zhì)上是定義參數(shù)并對(duì)參數(shù)進(jìn)行處理,而處理參數(shù)的邏輯一定是與所定義的參數(shù)有關(guān)聯(lián)的。那可不可以用函數(shù)和裝飾器來(lái)實(shí)現(xiàn)處理參數(shù)邏輯與定義參數(shù)的關(guān)聯(lián)呢?click?正好就是以這種使用方式來(lái)設(shè)計(jì)的。

裝飾器這樣一個(gè)優(yōu)雅的語(yǔ)法糖是元信息定義和處理邏輯之間的絕妙膠水,從而暗示了兩者的路有關(guān)系。對(duì)比于前兩個(gè)命令行庫(kù)的路由實(shí)現(xiàn)著實(shí)優(yōu)雅了不少。

在?click?的世界中:

通過(guò)裝飾器定義命令和參數(shù)的元信息
使用此裝飾器裝飾處理函數(shù)
對(duì),就是這么簡(jiǎn)單。

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

import sys
import click

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

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

2. 定義參數(shù)

@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. 業(yè)務(wù)邏輯

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()
從上述示例可以看到,元信息定義和處理邏輯無(wú)縫綁定在一起,能夠直觀地看出對(duì)應(yīng)的參數(shù)會(huì)如何處理,這個(gè)優(yōu)勢(shì)在有大量參數(shù)需要處理時(shí)顯得尤為突出。在處理函數(shù)中,接收到不再是像?argparse?或?docopt?中的一個(gè)包含所有參數(shù)的變量,而是具體的參數(shù)變量,這讓處理邏輯在參數(shù)使用上也變得更加簡(jiǎn)便。

此外,click?還內(nèi)置了很多實(shí)用工具和增強(qiáng)能力,如參數(shù)自動(dòng)補(bǔ)全、分頁(yè)支持、顏色、進(jìn)度條等功能,能夠有效提升開(kāi)發(fā)效率。

fire
雖然前面三個(gè)庫(kù)已經(jīng)足夠強(qiáng)大,但是仍然會(huì)有人認(rèn)為不夠簡(jiǎn)單。是否還有進(jìn)一步簡(jiǎn)化的空間呢?如果只是定義函數(shù),是否能讓框架推測(cè)出參數(shù)元信息呢?理論上還真是可以。

fire?用一種面向廣義對(duì)象的方式來(lái)玩轉(zhuǎn)命令行,這種對(duì)象可以是類(lèi)、函數(shù)、字典、列表等,它更加靈活,也更加簡(jiǎn)單。你都不需要定義參數(shù)類(lèi)型,fire?會(huì)根據(jù)輸入和參數(shù)默認(rèn)值來(lái)自動(dòng)判斷,這無(wú)疑進(jìn)一步簡(jiǎn)化了實(shí)現(xiàn)過(guò)程。

在?fire?的世界中,定義 Python 對(duì)象就夠了。

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

import sys
import fire

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

業(yè)務(wù)邏輯

類(lèi)中有幾個(gè)方法,就意味著命令行程序有幾個(gè)同名命令

class Calc:
# text 沒(méi)有任何默認(rèn)值,視為位置參數(shù)
# lower/upper 有布爾類(lèi)型的默認(rèn)值,視為選項(xiàng)參數(shù) --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?足夠的簡(jiǎn)單,一切都是根據(jù)約定來(lái)進(jìn)行推斷,包括支持哪些命令,每個(gè)命令接受的什么參數(shù)和選項(xiàng)。這種方式可以說(shuō)是足夠的 Pythonic,相比于?click,fire?把命令行參數(shù)的定義和函數(shù)參數(shù)的定義融為了一體。通過(guò)它,我們真的就只用關(guān)注業(yè)務(wù)邏輯。

不過(guò)簡(jiǎn)單往往也意味著對(duì)于復(fù)雜需求的捉襟見(jiàn)肘。僅僅通過(guò)默認(rèn)值來(lái)推導(dǎo)命令行參數(shù)所能表達(dá)的情況是有限的,比如互斥選項(xiàng)、位置參數(shù)的類(lèi)型限定都無(wú)法通過(guò)框架來(lái)表達(dá),而只能由業(yè)務(wù)邏輯去判斷。

typer
那么該如何在保持像?fire?這樣簡(jiǎn)單實(shí)現(xiàn)的方式下,增強(qiáng)參數(shù)元信息的表達(dá)能力呢?既然默認(rèn)參數(shù)的能力有限,那么如果使用 Python 3 的類(lèi)型注解呢?

typer?站在?click?巨人的肩膀上,借助 Python 3 類(lèi)型注解的特性,既滿(mǎn)足了簡(jiǎn)單直觀編寫(xiě)的需要,又達(dá)到了應(yīng)對(duì)復(fù)雜場(chǎng)景的目的,可謂是現(xiàn)代化的命令行庫(kù)。

在?typer?的世界中,也是直接編寫(xiě)業(yè)務(wù)邏輯,和?fire?稍稍不同的點(diǎn)是使用了類(lèi)型注解和默認(rèn)值來(lái)表達(dá)參數(shù)元信息定義。

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

import sys
import typer

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

定義命令 echo,及處理函數(shù)

text 無(wú)默認(rèn)值,視為位置參數(shù),類(lèi)型為字符串

lower/upper 類(lèi)型為 bool,默認(rèn)值為 False,視為選項(xiàng) --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,及處理函數(shù)

expression 無(wú)默認(rèn)值,視為位置參數(shù),類(lèi)型為字符串

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

cli()
從上面的示例可以看出,相比于?click,它免去了參數(shù)元信息的繁瑣定義,取而代之的是類(lèi)型注解;相比于?fire,它的元信息定義能力則大大增強(qiáng),可以通過(guò)指定默認(rèn)值為?typer.Option?或?typer.Argument?來(lái)進(jìn)一步擴(kuò)展參數(shù)和選項(xiàng)的語(yǔ)義。可以說(shuō)是,typer?達(dá)到了簡(jiǎn)單與靈活的完美平衡。

橫向?qū)Ρ?br /> 最后,我們橫向?qū)Ρ认?argparse、docopt、click、fire、typer?庫(kù)的各項(xiàng)功能和特點(diǎn):

argpase docopt click fire typer
使用步驟數(shù) 4 步 3 步 2 步 1 步 1 步
使用步驟數(shù) 1. 設(shè)置解析器
2. 定義參數(shù)
3. 解析命令行
4. 業(yè)務(wù)邏輯 1. 定義接口描述
2. 解析命令行
3. 業(yè)務(wù)邏輯 1. 業(yè)務(wù)邏輯
2. 定義參數(shù) 1. 業(yè)務(wù)邏輯 1 . 業(yè)務(wù)邏輯
選項(xiàng)參數(shù)
(如?--sum) ? ? ? ? ?
位置參數(shù)
(如?X Y) ? ? ? ? ?
參數(shù)默認(rèn)值 ? ? ? ? ?
互斥選項(xiàng)
(如?--car?和?--bus?只能二選一) ? ? ?
可通過(guò)第三方庫(kù)支持 ? ?
可變參數(shù)
(如指定多個(gè)?--file) ? ? ? ? ?
嵌套/父子命令 ? ? ? ? ?
工具箱 ? ? ? ? ?
鏈?zhǔn)矫钫{(diào)用 ? ? ? ? ?
類(lèi)型約束 ? ? ? ? ?
Python 的命令行庫(kù)種類(lèi)繁多、各具特色,它們并非是重復(fù)造輪子的產(chǎn)物,其背后的思想值得學(xué)習(xí)。結(jié)合橫向?qū)Ρ鹊目偨Y(jié),可以選擇出符合使用場(chǎng)景的庫(kù)。如果幾個(gè)庫(kù)都符合,那么就選擇你所偏愛(ài)的風(fēng)格。

原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

總結(jié)

以上是生活随笔為你收集整理的Python 命令行库的大乱的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。