如何用Plumbum開發(fā)Python命令行工具,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

公司主營業(yè)務(wù):成都做網(wǎng)站、網(wǎng)站制作、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)公司是一支青春激揚、勤奮敬業(yè)、活力青春激揚、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)公司推出義安免費做網(wǎng)站回饋大家。
主要介紹如何使用 Plumbum CLI 工具包來開發(fā) Python 命令行應(yīng)用程序,這是一個非常 Pythonic、容易使用、功能強(qiáng)大的工具包,非常值得廣大 Python 程序員掌握并使用。
輕松執(zhí)行程序的另一方面是輕松編寫 CLI 程序。Python 腳本一般使用 optparse 或者***的 argparse 及其衍生品來開發(fā)命令行工具,但是所有這些表現(xiàn)力有限,而且非常不直觀(甚至不夠 Pythonic)。Plumbum 的 CLI 工具包提供了一個程序化的方法來構(gòu)建命令行應(yīng)用程序,不需要創(chuàng)建一個解析器對象,然后填充一系列“選項”,該 CLI 工具包使用內(nèi)省機(jī)制將這些原語轉(zhuǎn)義成 Pythonic 結(jié)構(gòu)。
總體來看,Plumbum CLI 應(yīng)用程序是一個繼承自 plumbum.cli.Application 的類。這些類定義了一個 main() 方法,并且可選地公開出方法和屬性來作為命令行的選項。這些選項可能需要參數(shù),而任何剩余的位置參數(shù)會根據(jù) main 函數(shù)的聲明來將其賦予 main 方法。一個簡單的 CLI 應(yīng)用程序看起來像如下這樣:
from plumbum import cli class MyApp(cli.Application): verbose = cli.Flag(["v", "verbose"], help = "If given, I will be very talkative") def main(self, filename): print("I will now read {0}".format(filename)) if self.verbose: print("Yadda " * 200) if __name__ == "__main__": MyApp.run()你可以運行該程序:
$ python example.py fooI will now read foo $ python example.py --helpexample.py v1.0 Usage: example.py [SWITCHES] filenameMeta-switches: -h, --help Prints this help message and quits --version Prints the program's version and quits Switches: -v, --verbose If given, I will be very talkative
到現(xiàn)在為止,你只看到了非常基本的使用。我們現(xiàn)在開始探索該庫。
新版本 1.6.1: 你可以直接運行應(yīng)用程序 MyApp(),不需要參數(shù),也不需要調(diào)用 .main()。
Application類是你的應(yīng)用程序的“容器”,該“容器”由一個你需要實現(xiàn)的main()方法和任何數(shù)量公開選項函數(shù)和屬性。你的應(yīng)用程序的入口是類方法 run,該方法實例化你的類、解析參數(shù)、調(diào)用所有的選項函數(shù),然后使用給的位置參數(shù)來調(diào)用main()函數(shù)。為了從命令行運行你的應(yīng)用程序,你所要做的是:
if __name__ == "__main__": MyApp.run()
除了 run() 和 main(),Application 類還公開了兩個內(nèi)置的選項函數(shù):help() 和 version(),分別用于顯示幫助和程序的版本。默認(rèn)情況下,--hep 和 -h 會調(diào)用 help(),--version 和 -v 會調(diào)用 version(),這些函數(shù)被調(diào)用后會顯示相應(yīng)的信息然后退出(沒有處理任何其他選項)。
你可以通過定義類屬性來自定義 help() 和 version() 顯示的信息,比如 PROGNAME、 VERSION 和 DESCRIPTION。舉例:
class MyApp(cli.Application): PROGNAME = "Foobar" VERSION = "7.3"
新版本 1.6
該庫也支持終端字符顏色控制。你可以直接將 PROGNAME, VERSION 和 DESCRIPTION 變?yōu)閹ь伾淖址H绻憬o PROGNAME 設(shè)置了顏色,你會得到自定義的程序名字和顏色。使用方法字符串的顏色可以通過設(shè)置 COLOR_USAGE 來生效,不同選項組的顏色可以通過設(shè)置 COLOR_GROUPS 字典來生效。
舉例如下:
class MyApp(cli.Application): PROGNAME = colors.green VERSION = colors.blue | "1.0.2" COLOR_GROUPS = {"Meta-switches" : colors.bold & colors.yellow} opts = cli.Flag("--ops", help=colors.magenta | "This is help")SimpleColorCLI.py 1.0.2 Usage: SimpleColorCLI.py [SWITCHES] Meta-switches -h, --help Prints this help message and quits --help-all Print help messages of all subcommands and quit -v, --version Prints the program's version and quits Switches --ops This is help
switch 裝飾器是該 CLI 開發(fā)工具包的“靈魂”,它會公開你的 CLI 應(yīng)用程序的方法來作為 CLI 命令行選項,這些方法運行通過命令行來調(diào)用。我們測試下如下應(yīng)用:
class MyApp(cli.Application): _allow_root = False # provide a default @cli.switch("--log-to-file", str) def log_to_file(self, filename): """Sets the file into which logs will be emitted""" logger.addHandler(FileHandle(filename)) @cli.switch(["-r", "--root"]) def allow_as_root(self): """If given, allow running as root""" self._allow_root = True def main(self): if os.geteuid() == 0 and not self._allow_root: raise ValueError("cannot run as root")當(dāng)程序運行時,選項函數(shù)通過相應(yīng)的參數(shù)被調(diào)用。比如,$ ./myapp.py --log-to-file=/tmp/log 將被轉(zhuǎn)化成調(diào)用 app.log_to_file("/tmp/log")。在選項函數(shù)被執(zhí)行后,程序的控制權(quán)會被傳遞到 main 方法。
注意
方法的文檔字符串和參數(shù)名字會被用來渲染幫助信息,盡量保持你的代碼 DRY。
autoswitch可以從函數(shù)名字中推斷出選項的名稱,舉例如下:@cli.autoswitch(str)def log_to_file(self, filename): pass這會將選項函數(shù)和
--log-to-file綁定。
如上面例子所示,選項函數(shù)可能沒有參數(shù)(不包括 self)或者有一個參數(shù)。如果選項函數(shù)接受一個參數(shù),必須指明該參數(shù)的類型。如果你不需要特殊的驗證,只需傳遞 str,否則,您可能會傳遞任何類型(或?qū)嶋H上可調(diào)用的任何類型),該類型將接收一個字符串并將其轉(zhuǎn)換為有意義的對象。如果轉(zhuǎn)換是不可行的,那么會拋出 TypeError 或者 ValueError 異常。
舉例:
class MyApp(cli.Application): _port = 8080 @cli.switch(["-p"], int) def server_port(self, port): self._port = port def main(self): print(self._port)
$ ./example.py -p 1717$ ./example.py -p fooArgument of -p expected to be <type 'int'>, not 'foo': ValueError("invalid literal for int() with base 10: 'foo'",)工具包包含兩個額外的“類型”(或者是是驗證器):Range 和 Set。Range 指定一個最小值和***值,限定一個整數(shù)在該范圍內(nèi)(閉區(qū)間)。Set 指定一組允許的值,并且期望參數(shù)匹配這些值中的一個。示例如下:
class MyApp(cli.Application): _port = 8080 _mode = "TCP" @cli.switch("-p", cli.Range(1024,65535)) def server_port(self, port): self._port = port @cli.switch("-m", cli.Set("TCP", "UDP", case_sensitive = False)) def server_mode(self, mode): self._mode = mode def main(self): print(self._port, self._mode)$ ./example.py -p 17Argument of -p expected to be [1024..65535], not '17': ValueError('Not in range [1024..65535]',)$ ./example.py -m fooArgument of -m expected to be Set('udp', 'tcp'), not 'foo': ValueError("Expected one of ['UDP', 'TCP']",)注意工具包中還有其他有用的驗證器:
ExistingFile(確保給定的參數(shù)是一個存在的文件),ExistingDirectory(確保給定的參數(shù)是一個存在的目錄),NonexistentPath(確保給定的參數(shù)是一個不存在的路徑)。所有這些將參數(shù)轉(zhuǎn)換為本地路徑。
很多時候,你需要在同一個命令行中多次指定某個選項。比如,在 gcc 中,你可能使用 -I 參數(shù)來引入多個目錄。默認(rèn)情況下,選項只能指定一次,除非你給 switch 裝飾器傳遞 list = True 參數(shù)。
class MyApp(cli.Application): _dirs = [] @cli.switch("-I", str, list = True) def include_dirs(self, dirs): self._dirs = dirs def main(self): print(self._dirs)$ ./example.py -I/foo/bar -I/usr/include['/foo/bar', '/usr/include']
注意選項函數(shù)只被調(diào)用一次,它的參數(shù)將會變成一個列表。
如果某個選項是必須的,你可以給 switch 裝飾器傳遞 mandatory = True 來實現(xiàn)。這樣的話,如果用戶不指定該選項,那么程序是無法運行的。
很多時候,一個選項的出現(xiàn)依賴另一個選項,比如,如果不給定 -y 選項,那么 -x 選項是無法給定的。這種限制可以通過給 switch 裝飾器傳遞 requires 參數(shù)來實現(xiàn),該參數(shù)是一個當(dāng)前選項所依賴的選項名稱列表。如果不指定某個選項所依賴的其他選項,那么用戶是無法運行程序的。
class MyApp(cli.Application): @cli.switch("--log-to-file", str) def log_to_file(self, filename): logger.addHandler(logging.FileHandler(filename)) @cli.switch("--verbose", requires = ["--log-to-file"]) def verbose(self): logger.setLevel(logging.DEBUG)$ ./example --verboseGiven --verbose, the following are missing ['log-to-file']
警告選項函數(shù)的調(diào)用順序和命令行指定的選項的順序是一致的。目前不支持在程序運行時計算選項函數(shù)調(diào)用的拓?fù)漤樞颍菍頃倪M(jìn)。
有些選項依賴其他選項,但是有些選項是和其他選項互斥的。比如,--verbose 和 --terse 同時存在是不合理的。為此,你可以給 switch 裝飾器指定 excludes 列表來實現(xiàn)。
class MyApp(cli.Application): @cli.switch("--log-to-file", str) def log_to_file(self, filename): logger.addHandler(logging.FileHandler(filename)) @cli.switch("--verbose", requires = ["--log-to-file"], excludes = ["--terse"]) def verbose(self): logger.setLevel(logging.DEBUG) @cli.switch("--terse", requires = ["--log-to-file"], excludes = ["--verbose"]) def terse(self): logger.setLevel(logging.WARNING)$ ./example --log-to-file=log.txt --verbose --terseGiven --verbose, the following are invalid ['--terse']
如果你希望在幫助信息中將某些選項組合在一起,你可以給 switch 裝飾器指定 group = "Group Name", Group Name 可以是任意字符串。當(dāng)顯示幫助信息的時候,所有屬于同一個組的選項會被聚合在一起。注意,分組不影響選項的處理,但是可以增強(qiáng)幫助信息的可讀性。
很多時候只需要將選項的參數(shù)存儲到類的屬性中,或者當(dāng)某個屬性給定后設(shè)置一個標(biāo)志。為此,工具包提供了 SwitchAttr,這是一個數(shù)據(jù)描述符,用來存儲參數(shù)。 該工具包還提供了兩個額外的 SwitchAttr:Flag(如果選項給定后,會給其賦予默認(rèn)值)和 CountOf (某個選項出現(xiàn)的次數(shù))。
class MyApp(cli.Application): log_file = cli.SwitchAttr("--log-file", str, default = None) enable_logging = cli.Flag("--no-log", default = True) verbosity_level = cli.CountOf("-v") def main(self): print(self.log_file, self.enable_logging, self.verbosity_level)$ ./example.py -v --log-file=log.txt -v --no-log -vvvlog.txt False 5
新版本 1.6
你可以使用 envname 參數(shù)將環(huán)境變量作為 SwitchAttr 的輸入。舉例如下:
class MyApp(cli.Application): log_file = cli.SwitchAttr("--log-file", str, envname="MY_LOG_FILE") def main(self): print(self.log_file)$ MY_LOG_FILE=this.log ./example.pythis.log
在命令行給定變量值會覆蓋相同環(huán)境變量的值。
一旦當(dāng)所有命令行參數(shù)被處理后 ,main() 方法會獲取程序的控制,并且可以有任意數(shù)量的位置參數(shù),比如,在 cp -r /foo /bar 中, /foo 和 /bar 是位置參數(shù)。程序接受位置參數(shù)的數(shù)量依賴于 main() 函數(shù)的聲明:如果 main 方法有 5 個參數(shù),2 個是有默認(rèn)值的,那么用戶最少需要提供 3 個位置參數(shù)并且總數(shù)量不能多于 5 個。如果 main 方法的聲明中使用的是可變參數(shù)(*args),那么位置參數(shù)的個數(shù)是沒有限制的。
class MyApp(cli.Application): def main(self, src, dst, mode = "normal"): print(src, dst, mode)
$ ./example.py /foo /bar/foo /bar normal$ ./example.py /foo /bar spam/foo /bar spam$ ./example.py /fooExpected at least 2 positional arguments, got ['/foo']$ ./example.py /foo /bar spam baconExpected at most 3 positional arguments, got ['/foo', '/bar', 'spam', 'bacon']
注意該方法的聲明也用于生成幫助信息,例如:
Usage: [SWITCHES] src dst [mode='normal']
使用可變參數(shù):
class MyApp(cli.Application): def main(self, src, dst, *eggs): print(src, dst, eggs)
$ ./example.py a b c da b ('c', 'd')$ ./example.py --helpUsage: [SWITCHES] src dst eggs...Meta-switches: -h, --help Prints this help message and quits -v, --version Prints the program's version and quits新版本 1.6
你可以使用 cli.positional 裝飾器提供的驗證器來驗證位置參數(shù)。只需在裝飾器中傳遞與 main 函數(shù)中的相匹配的驗證器即可。例如:
class MyApp(cli.Application): @cli.positional(cli.ExistingFile, cli.NonexistentPath) def main(self, infile, *outfiles): "infile is a path, outfiles are a list of paths, proper errors are given"
如果你的程序只在 Python 3 中運行,你可以使用注解來指定驗證器,例如:
class MyApp(cli.Application): def main(self, infile : cli.ExistingFile, *outfiles : cli.NonexistentPath): "Identical to above MyApp"
如果 positional 裝飾器存在,那么注解會被忽略。
新版本 1.1
隨著 CLI 應(yīng)用程序的擴(kuò)展,功能變的越來越多,一個通常的做法是將其邏輯分成多個子應(yīng)用(或者子命令)。一個典型的例子是版本控制系統(tǒng),比如 git,git 是根命令,在這之下的子命令比如 commit 或者 push 是嵌套的。git 甚至支持命令別名,這運行用戶自己創(chuàng)建一些子命令。Plumbum 寫類似這樣的程序是很輕松的。
在我們開始了解代碼之前,先強(qiáng)調(diào)兩件事情:
在 Plumbum 中,每個子命令都是一個完整的 cli.Application 應(yīng)用,你可以單獨執(zhí)行它,或者從所謂的根命令中分離出來。當(dāng)應(yīng)用程序單獨執(zhí)行是,它的父屬性是 None,當(dāng)以子命令運行時,它的父屬性指向父應(yīng)用程序。同樣,當(dāng)父應(yīng)用使用子命令執(zhí)行時,它的內(nèi)嵌命令被設(shè)置成內(nèi)嵌應(yīng)用。
每個子命令只負(fù)責(zé)它自己的選項參數(shù)(直到下一個子命令)。這允許應(yīng)用在內(nèi)嵌應(yīng)用調(diào)用之前來處理它自己的選項和位置參數(shù)。例如 git --foo=bar spam push origin --tags:根應(yīng)用 git 負(fù)責(zé)選項 --foo 和位置選項 spam ,內(nèi)嵌應(yīng)用 push 負(fù)責(zé)在它之后的參數(shù)。從理論上講,你可以將多個子應(yīng)用程序嵌套到另一個應(yīng)用程序中,但在實踐中,通常嵌套層級只有一層。
這是一個模仿版本控制系統(tǒng)的例子 geet。我們有一個根應(yīng)用 Geet ,它有兩個子命令 GeetCommit 和 GeetPush:這兩個子命令通過 subcommand 裝飾器來將其附加到根應(yīng)用。
class Geet(cli.Application): """The l33t version control""" VERSION = "1.7.2" def main(self, *args): if args: print("Unknown command {0!r}".format(args[0])) return 1 # error exit code if not self.nested_command: # will be ``None`` if no sub-command follows print("No command given") return 1 # error exit code @Geet.subcommand("commit") # attach 'geet commit'class GeetCommit(cli.Application): """creates a new commit in the current branch""" auto_add = cli.Flag("-a", help = "automatically add changed files") message = cli.SwitchAttr("-m", str, mandatory = True, help = "sets the commit message") def main(self): print("doing the commit...") @Geet.subcommand("push") # attach 'geet push'class GeetPush(cli.Application): """pushes the current local branch to the remote one""" def main(self, remote, branch = None): print("doing the push...") if __name__ == "__main__": Geet.run()注意
由于
GeetCommit也是一個cli.Application,因此你可以直接調(diào)用GeetCommit.run()(這在應(yīng)用的上下文是合理的)你也可以不用裝飾器而使用
subcommand方法來附加子命令:Geet.subcommand("push", GeetPush)
以下是運行該應(yīng)用程序的示例:
$ python geet.py --helpgeet v1.7.2The l33t version control Usage: geet.py [SWITCHES] [SUBCOMMAND [SWITCHES]] args...Meta-switches: -h, --help Prints this help message and quits -v, --version Prints the program's version and quits Subcommands: commit creates a new commit in the current branch; see 'geet commit --help' for more info push pushes the current local branch to the remote one; see 'geet push --help' for more info $ python geet.py commit --helpgeet commit v1.7.2creates a new commit in the current branch Usage: geet commit [SWITCHES]Meta-switches: -h, --help Prints this help message and quits -v, --version Prints the program's version and quits Switches: -a automatically add changed files -m VALUE:str sets the commit message; required $ python geet.py commit -m "foo"doing the commit...
應(yīng)用程序的另一個常見的功能是配置文件解析器,解析后臺 INI 配置文件:Config (或者 ConfigINI)。使用示例:
from plumbum import cli with cli.Config('~/.myapp_rc') as conf: one = conf.get('one', '1') two = conf.get('two', '2')如果配置文件不存在,那么將會以當(dāng)前的 key 和默認(rèn)的 value 來創(chuàng)建一個配置文件,在調(diào)用 .get 方法時會得到默認(rèn)值,當(dāng)上下文管理器存在時,文件會被創(chuàng)建。如果配置文件存在,那么該文件將會被讀取并且沒有任何改變。你也可以使用 [] 語法來強(qiáng)制設(shè)置一個值或者當(dāng)變量不存在時獲取到一個 ValueError。如果你想避免上下文管理器,你也可以使用 .read 和 .write。
ini 解析器默認(rèn)使用 [DEFAULT] 段,就像 Python 的 ConfigParser。如果你想使用一個不同的段,只需要在 key 中通過 . 將段和標(biāo)題分隔開。比如 conf['section.item'] 會將 item 放置在 [section] 下。所有存儲在 ConfigINI 中的條目會被轉(zhuǎn)化成 str,str 是經(jīng)常返回的。
在 plumbum.cli.terminal 中有多個終端實用程序,用來幫助制作終端應(yīng)用程序。
get_terminal_size(default=(80,25)) 允許跨平臺訪問終端屏幕大小,返回值是一個元組 (width, height)。還有幾個方法可以用來詢問用戶輸入,比如 readline, ask, choose 和 prompt 都是可用的。
Progress(iterator) 可以使你快速地從迭代器來創(chuàng)建一個進(jìn)度條。簡單地打包一個 slow 迭代器并迭代就會生成一個不錯的基于用戶屏幕寬度的文本進(jìn)度條,同時會顯示剩余時間。如果你想給 fast 迭代器創(chuàng)建一個進(jìn)度條,并且在循環(huán)中包含代碼,那么請使用 Progress.wrap 或者 Progress.range。例如:
for i in Progress.range(10): time.sleep(1)
如果在終端中有其他輸出,但是仍然需要一個進(jìn)度條,請傳遞 has_output=True 參數(shù)來禁止進(jìn)度條清除掉歷史輸出。
在 plumbum.cli.image 中提供了一個命令行繪圖器(Image)。它可以繪制一個類似 PIL 的圖像:
Image().show_pil(im)
Image 構(gòu)造函數(shù)接受一個可選的 size 參數(shù)(如果是 None,那么默認(rèn)是當(dāng)前終端大小)和一個字符比例,該比例來自當(dāng)前字符的高度和寬度的度量,默認(rèn)值是 2.45。如果設(shè)置為 None,ratio 將會被忽略,圖像不再被限制成比例縮放。要直接繪制一個圖像,show 需要一個文件名和一對參數(shù)。show_pil 和 show_pil_double 方法直接接受一個 PIL-like 對象。為了從命令行繪制圖像,該模塊可以直接被運行:python -m plumbum.cli.image myimage.png。
看完上述內(nèi)容,你們掌握如何用Plumbum開發(fā)Python命令行工具的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
當(dāng)前標(biāo)題:如何用Plumbum開發(fā)Python命令行工具
網(wǎng)頁路徑:http://chinadenli.net/article46/jggdhg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、軟件開發(fā)、網(wǎng)站設(shè)計、網(wǎng)站設(shè)計公司、域名注冊、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)