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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python 命令行库的大乱

發布時間:2024/8/23 python 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 的命令行庫種類繁多、各具特色,它們并非是重復造輪子的產物,其背后的思想值得學習。結合橫向對比的總結,可以選擇出符合使用場景的庫。如果幾個庫都符合,那么就選擇你所偏愛的風格。

原文鏈接
本文為阿里云原創內容,未經允許不得轉載。

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。