使用Python編寫命令行工具有什麼好的庫?


謝邀

推薦 click

它是Python內置庫Argparse基礎上進行的封裝,用起來特別簡單,用command裝飾函數,使之成為可執行的命令,使用option 來指定接收參數

看個官方例子:

import click

@click.command()
@click.option(--count, default=1, help=Number of greetings.)
@click.option(--name, prompt=Your name,
help=The person to greet.)
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo(Hello %s! % name)

if __name__ == __main__:
hello()

執行:

$ python hello.py --count=3
Your name: John
Hello John!
...

Google 的 Python Fire 也是一個值得嘗試的工具


使用Python編寫命令行工具的庫很多,我最推薦的還是Google Fire

Hello World

要介紹Fire是什麼,看一個簡單的例子就明白了

# calc.py
import fire

class Calculator(object):
"""A simple calculator class."""
def double(self, number):
return 2 * number

if __name__ == __main__:
fire.Fire(Calculator)

接下來我們進入bash來執行上面編寫的腳本

&> python calc.py double 10
20
&> python calc.py double --number=16
32

上面是官方的示例代碼,有了fire,編寫Python的命令行程序就變得非常簡單,我們無需再去處理繁瑣的命令行參數解析了。接下來我們仿照HelloWorld,編寫一個圓周率和階乘計算的命令行腳本。

實戰

import math
import fire

class Math(object):

def pi(self, n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return math.sqrt(6*s)

def fact(self, n):
s = 1
for i in range(n):
s *= (i+1)
return s

if __name__ == __main__:
fire.Fire(Math)

接下來我們運行一下

&> python maths.py pi 10000
3.14149716395
&> python maths.py pi 100000
3.14158310433
&> python maths.py pi 1000000
3.14159169866
&> python maths.py fact 10
3628800
&> python maths.py fact 15
1307674368000
&> python maths.py fact 20
2432902008176640000

Cool,真的非常方便!fire對當前對象結構進行了暴露,將結構信息映射到shell命令行參數上。fire其實有多種暴露模式,接下來我們逐個來看fire都有哪些暴露模式。

暴露模塊

fire如果不傳遞任何參數就可以直接暴露當前模塊結構,我們對上面的例子做一下改造,去掉類信息

import math
import fire

def pi(n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return math.sqrt(6*s)

def fact(n):
s = 1
for i in range(n):
s *= (i+1)
return s

if __name__ == __main__:
fire.Fire()

注意Fire函數調用沒有任何參數,運行一下

&> python maths.py fact 20
2432902008176640000
&> python maths.py pi 1000000
3.14159169866

暴露函數

fire還可以傳遞一個函數對象來暴露單個函數,可以讓我們在命令行參數上省掉函數名稱

import math
import fire
def pi(n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return math.sqrt(6*s)
if __name__ == __main__:
fire.Fire(pi)

如果暴露函數那就只能暴露一個函數,如果暴露了兩個,那就只有後面一個生效,運行一下

&> python maths.py 1000
3.14063805621

暴露字典

fire可以直接暴露一個模塊,將當前模塊的所有函數全部暴露,函數名和第一個參數名一致。我們也可以不用暴露整個模塊的所有函數,使用字典暴露法就可以選擇性地對模塊的某些函數進行暴露,順便還可以替換暴露出來的函數名稱。

import math
import fire

def pi(n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return math.sqrt(6*s)

def fact(n):
s = 1
for i in range(n):
s *= (i+1)
return s

if __name__ == __main__:
fire.Fire({
"pi[n]": pi
})

我們只暴露了pi函數,並且把名字還換掉了,運行一下,看效果

&> python maths.py pi[n] 1000
3.14063805621

如果我們使用原函數名稱,就會看到fire列出的友好的報錯信息

&> python maths.py pi 1000
Fire trace:
1. Initial component
2. (Cannot find target in dict:, pi, {pi[n]: &})
Type: dict
String form: {pi[n]: &}
Length: 1
Usage: maths.py
maths.py pi[n]

暴露對象

import math
import fire

class Maths(object):

def pi(self, n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return math.sqrt(6*s)

def fact(self, n):
s = 1
for i in range(n):
s *= (i+1)
return s

if __name__ == __main__:
fire.Fire(Maths())

運行

&> python maths.py pi 1000
3.14063805621
&> python maths.py fact 20
2432902008176640000

暴露類

這個我們在上面的實戰環節已經演示過了,這裡就不在重複粘貼

類 vs 對象

通過上面的例子,我們發現暴露類和暴露對象似乎沒有任何區別,那到底該選哪種比較優雅呢?這個要看類的構造器有沒有參數,如果是不帶參數的構造器,那麼類和對象的暴露是沒有區別的,但是如果類的構造器有參數,那就不一樣了,下面我們改造一下Maths類,增加一個放大係數。

import math
import fire

class Maths(object):

def __init__(self, coeff):
self.coeff = coeff

def pi(self, n):
s = 0.0
for i in range(n):
s += 1.0/(i+1)/(i+1)
return self.coeff * math.sqrt(6*s)

def fact(self, n):
s = 1
for i in range(n):
s *= (i+1)
return self.coeff * s

if __name__ == __main__:
fire.Fire(Maths)

因為Maths的構造器帶有參數,所有運行命令行時需要指定構造器參數值

&> python maths.py pi 1000 --coeff=2
6.28127611241

如果不指定參數的值,運行時就會報錯

&> python maths.py pi 1000
Fire trace:
1. Initial component
2. (The function received no value for the required argument:, coeff)
Type: type
String form: &
File: ~/source/rollado/maths.py
Line: 5
Usage: maths.py COEFF
maths.py --coeff COEFF

如果改成暴露對象,那麼放大係數就是在代碼里寫死的,無法在命令行進行參數定製了。這就是暴露對象和暴露類的差別,似乎暴露類在功能上更強大一些。

暴露屬性

上面的所有例子我們最終暴露的都是函數,要麼是模塊里的函數,要麼是類里的函數。但實際上fire還可以暴露屬性,比如我們可以將上面的coeff參數通過命令行進行輸出。

&> python maths.py coeff --coeff=2
2
&> python maths.py coeff --coeff=3
3

再來一個更加簡單的例子

# example.py
import fire
english = Hello World
spanish = Hola Mundo
fire.Fire()

運行

$ python example.py english
Hello World
$ python example.py spanish
Hola Mundo

原理

命令行中的參數順序和代碼內部對象的樹狀層次結構呈現一一對應關係。如果fire不帶參數暴露了當前的模塊,那麼第一個參數就應該是這個模塊內部的函數名、類名或者是變數名。如果第一個參數是函數,那麼接下來的參數就是函數的參數。如果第一個參數是類,那麼接下來的參數可能是這個類實例內部的方法或者欄位。如果第一個參數是變數名,後面沒有參數的話,就直接顯示這個變數。如果後面還有參數,那麼就把這個變數看成一個對象,然後繼續使用後續參數來深入解析這個對象。

在Python裡面所有的變數都是對象,包括普通的整數、字元串、浮點數、布爾值等。理論上可以一直將對象結構遞歸下去,形成一個複雜的鏈式調用。

鏈式暴露

接下來我們驗證這個理論,嘗試一下複雜的鏈式暴露。

import fire

class Chain(object):

def __init__(self):
self.value = 1

def incr(self):
print "incr", self.value
self.value += 1
return self

def decr(self):
print "decr", self.value
self.value -= 1
return self

def get(self):
return self.value

if __name__ == __main__:
fire.Fire(Chain)

運行一下

&> python chains.py incr incr incr decr decr get
incr 1
incr 2
incr 3
decr 4
decr 3
2

Cool! 我們通過在每個方法裡面方法self對象自身來實現了漂亮的鏈式調用效果。

接下來我們嘗試對內置字元串對象進行解構

# xyz.py
import fire

value = "hello"

if __name__ == __main__:
fire.Fire()

字元串有upper和lower方法,我們反覆使用upper和lower,然後觀察結果

&> python xyz.py value
hello
&> python xyz.py value upper
HELLO
&> python xyz.py value upper lower
Traceback (most recent call last):
File "xyz.py", line 7, in &
fire.Fire()
File "/Users/pyloque/source/pys/.py/lib/python2.7/site-packages/fire/core.py", line 127, in Fire
component_trace = _Fire(component, args, context, name)
File "/Users/pyloque/source/pys/.py/lib/python2.7/site-packages/fire/core.py", line 366, in _Fire
component, remaining_args)
File "/Users/pyloque/source/pys/.py/lib/python2.7/site-packages/fire/core.py", line 542, in _CallCallable
result = fn(*varargs, **kwargs)
TypeError: upper() takes no arguments (1 given)

很不幸,內置的字元串對象似乎不支持鏈式調用,第一個upper倒是執行成功了。不過fire提供了一個特殊的符號用來解決這個問題。

&> python xyz.py value upper - lower
hello
&> python xyz.py value upper - lower - upper
HELLO
&> python xyz.py value upper - lower - upper - lower
hello

減號用來表示參數的結束,這樣後續的參數就不會被當成函數的參數來映射了。

讓redis-py秒變命令行

最後我們再來一個酷炫的小例子,把redis-py的StrictRedis暴露一下變身命令行

import fire
import redis

if __name__ == __main__:
fire.Fire(redis.StrictRedis)

就這麼簡單,接下來就可以玩命令行了

&> python client.py flushdb
True
&> python client.py set codehole superhero
True
&> python client.py get codehole
superhero
&> python client.py exists codehole
True
&> python client.py keys "*"
codehole
&> python client.py delete codehole
1
# 指定地址
&> python client.py set codehole superhero --host=127.0.0.1 --port=6379
True

總結

有了Google Fire這樣一個小巧的類庫,我們就可以從複雜的命令行參數分析中解脫出來了。我們常說寫代碼要漂亮優雅,沒有好的類庫,這種理想也不是非常容易實現的。


click是Python的一個命令行工具,極其好用。不信?一試便知。

前言

我們的遊戲資源處理工具是Python實現的,功能包括csv解析,UI材質處理,動畫資源解析、批處理,AndrodiOS自動打包等功能。該項目是由其他部門繼承過來的,由於絕大部分代碼不符合我們的業務需求,所以進行了大重構。刪除了所有業務代碼,僅保留了python代碼框架。項目中命令行參數解析是自己實現的,極其不優雅,也忍了這麼久。打算找時間用click重寫。所以最近學習了click,下面本文的內容是click的入門教程,初學者們可以來一起學習學習。

官網鏡像地址: http://click.uoota.com/6/

支持:

命令的任意嵌套

自動生成幫助信息

支持在運行時子命令的延遲載入

下面一小段代碼是其官方主頁的例子,貼出來下:

運行:

查看幫助信息:

Thats all(攤手)!


自己造的輪子,ugly-code

http://github.com/irealing/ugly-code

或者

pip install ugly-code


推薦閱讀:

淺談命令行(一):命令行基本操作(包括vim在終端的基本操作)
滿滿逼格的win10個性化命令cmd
淺談命令行(四):好玩的命令行操作(有好玩再補充)
被黑客們濫用的Windows命令

TAG:Python | 命令行界面CLI | 命令行控制 |