1. 程式人生 > >Python 命令列之旅:深入 click 之增強功能

Python 命令列之旅:深入 click 之增強功能



作者:HelloGitHub-Prodesire

HelloGitHub 的《講解開源專案》系列,專案地址:https://github.com/HelloGitHub-Team/Article

一、前言

在前面三篇文章中,我們介紹了 click 中的引數、選項和命令,本文將介紹 click 錦上添花的功能,以幫助我們更加輕鬆地打造一個更加強大的命令列程式。

本系列文章預設使用 Python 3 作為直譯器進行講解。
若你仍在使用 Python 2,請注意兩者之間語法和庫的使用差異哦~

二、增強功能

2.1 Bash 補全

Bash 補全是 click 提供的一個非常便捷和強大的功能,這是它比 argpase

docopt 強大的一個表現。

在命令列程式正確安裝後,Bash 補全才可以使用。而如何安裝可以參考 setup 整合。Click 目前僅支援 Bash 和 Zsh 的補全。

2.1.1 補全能力

通常來說,Bash 補全支援對子命令、選項、以及選項或引數值得補全。比如:

$ repo <TAB><TAB>
clone    commit   copy     delete   setuser
$ repo clone -<TAB><TAB>
--deep     --help     --rev      --shallow  -r

此外,click

還支援自定義補全,這在動態生成補全場景中很有用,使用 autocompletion 引數。autocompletion 需要指定為一個回撥函式,並且返回字串的列表。此函式接受三個引數:

  • ctx —— 當前的 click 上下文
  • args 傳入的引數列表
  • incomplete 正在補全的詞

這裡有一個根據環境變數動態生成補全的示例:

import os

def get_env_vars(ctx, args, incomplete):
    return [k for k in os.environ.keys() if incomplete in k]

@click.command()
@click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
def cmd1(envvar):
    click.echo('Environment variable: %s' % envvar)
    click.echo('Value: %s' % os.environ[envvar])

ZSH 中,還支援補全幫助資訊。只需將 autocompletion 回撥函式中返回的字串列表中的字串改為二元元組,第一個元素是補全內容,第二個元素是幫助資訊。

這裡有一個顏色補全的示例:

import os

def get_colors(ctx, args, incomplete):
    colors = [('red', 'help string for the color red'),
              ('blue', 'help string for the color blue'),
              ('green', 'help string for the color green')]
    return [c for c in colors if incomplete in c[0]]

@click.command()
@click.argument("color", type=click.STRING, autocompletion=get_colors)
def cmd1(color):
    click.echo('Chosen color is %s' % color)

2.1.2 啟用補全

要啟用 Bash 的補全功能,就需要告訴它你的命令列程式有補全的能力。通常通過一個神奇的環境變數 _<PROG_NAME>_COMPLETE 來告知,其中 <PROG_NAME> 是大寫下劃線形式的程式名稱。

比如有一個命令列程式叫做 foo-bar,那麼對應的環境變數名稱為 _FOO_BAR_COMPLETE,然後在 .bashrc 中使用 source 匯出即可:

eval "$(_FOO_BAR_COMPLETE=source foo-bar)"

或者在 .zshrc 中使用:

eval "$(_FOO_BAR_COMPLETE=source_zsh foo-bar)"

不過上面的方式總是在命令列程式啟動時呼叫,這可能在有多個程式時減慢 shell 啟用的速度。另一種方式是把命令放在檔案中,就像這樣:

# 針對 Bash
_FOO_BAR_COMPLETE=source foo-bar > foo-bar-complete.sh

# 針對 ZSH
_FOO_BAR_COMPLETE=source_zsh foo-bar > foo-bar-complete.sh

然後把指令碼檔案路徑加到 .bashrc.zshrc 中:

. /path/to/foo-bar-complete.sh

2.2 實用工具

2.2.1 列印到標準輸出

echo() 函式可以說是最有用的實用工具了。它和 Python 的 print 類似,主要的區別在於它同時在 Python 2 和 3 中生效,能夠智慧地檢測未配置正確的輸出流,且幾乎不會失敗(除了 Python 3 中的少數限制。)

echo 即支援 unicode,也支援二級制資料,如:

import click

click.echo('Hello World!')

click.echo(b'\xe2\x98\x83', nl=False) # nl=False 表示不輸出換行符

2.2.2 ANSI 顏色

有些時候你可能希望輸出是有顏色的,這尤其在輸出錯誤資訊時有用,而 click 在這方面支援的很好。

首先,你需要安裝 colorama

pip install colorama

然後,就可以使用 style() 函式來指定顏色:

import click

click.echo(click.style('Hello World!', fg='green'))
click.echo(click.style('Some more text', bg='blue', fg='white'))
click.echo(click.style('ATTENTION', blink=True, bold=True))

click 還提供了更加簡便的函式 secho,它就是 echostyle 的組合:

click.secho('Hello World!', fg='green')
click.secho('Some more text', bg='blue', fg='white')
click.secho('ATTENTION', blink=True, bold=True)

2.2.3 分頁支援

有些時候,命令列程式會輸出長文字,但你希望能讓使用者盤也瀏覽。使用 echo_via_pager() 函式就可以輕鬆做到。

例如:

def less():
    click.echo_via_pager('\n'.join('Line %d' % idx
                                   for idx in range(200)))

如果輸出的文字特別大,處於效能的考慮,希望翻頁時生成對應內容,那麼就可以使用生成器:

def _generate_output():
    for idx in range(50000):
        yield "Line %d\n" % idx

@click.command()
def less():
    click.echo_via_pager(_generate_output())

2.2.4 清除螢幕

使用 clear() 可以輕鬆清除螢幕內容:

import click
click.clear()

2.2.5 從終端獲取字元

通常情況下,使用內建函式 inputraw_input 獲得的輸入是使用者輸出一段字元然後回車得到的。但在有些場景下,你可能想在使用者輸入單個字元時就能獲取到並且做一定的處理,這個時候 getchar() 就派上了用場。

比如,根據輸入的 yn 做特定處理:

import click

click.echo('Continue? [yn] ', nl=False)
c = click.getchar()
click.echo()
if c == 'y':
    click.echo('We will go on')
elif c == 'n':
    click.echo('Abort!')
else:
    click.echo('Invalid input :(')

2.2.6 等待按鍵

在 Windows 的 cmd 中我們經常看到當執行完一個命令後,提示按下任意鍵退出。通過使用 pause() 可以實現暫停直至使用者按下任意鍵:

import click
click.pause()

2.2.7 啟動編輯器

通過 edit() 可以自動啟動編輯器。這在需要使用者輸入多行內容時十分有用。

在下面的示例中,會啟動預設的文字編輯器,並在裡面輸入一段話:

import click

def get_commit_message():
    MARKER = '# Everything below is ignored\n'
    message = click.edit('\n\n' + MARKER)
    if message is not None:
        return message.split(MARKER, 1)[0].rstrip('\n')

edit() 函式還支援開啟特定檔案,比如:

import click
click.edit(filename='/etc/passwd')

2.2.8 啟動應用程式

通過 launch 可以開啟 URL 或檔案型別所關聯的預設應用程式。如果設定 locate=True,則可以啟動檔案管理器並自動選中特定檔案。

示例:

# 開啟瀏覽器,訪問 URL
click.launch("https://click.palletsprojects.com/")

# 使用預設應用程式開啟 txt 檔案
click.launch("/my/downloaded/file.txt")

# 開啟檔案管理器,並自動選中 file.txt
click.launch("/my/downloaded/file.txt", locate=True)

2.2.9 顯示進度條

click 內建了 progressbar() 函式來方便地顯示進度條。

它的用法也很簡單,假定你有一個要處理的可迭代物件,處理完每一項就要輸出一下進度,那麼就有兩種用法。

用法一:使用 progressbar 構造出 bar 物件,迭代 bar 物件來自動告知進度:

import time
import click

all_the_users_to_process = ['a', 'b', 'c']

def modify_the_user(user):
    time.sleep(0.5)

with click.progressbar(all_the_users_to_process) as bar:
    for user in bar:
        modify_the_user(user)

用法二:使用 progressbar 構造出 bar 物件,迭代原始可迭代物件,並不斷向 bar 更新進度:

import time
import click

all_the_users_to_process = ['a', 'b', 'c']

def modify_the_user(user):
    time.sleep(0.5)

with click.progressbar(all_the_users_to_process) as bar:
    for user in enumerate(all_the_users_to_process):
        modify_the_user(user)
        bar.update(1)

2.2.10 更多實用工具

  • 列印檔名
  • 標準流
  • 智慧開啟檔案
  • 查詢應用程式資料夾

三、總結

click 提供了非常多的增強型功能,本文著重介紹了它的 Bash 補全和十多個實用工具,這會讓你在實現命令列的過程中如虎添翼。此外,click 還提供了諸如命令別名、引數修改、標準化令牌、呼叫其他命令、回撥順序等諸多高階模式 以應對更加複雜或特定的場景,我們就不再深入介紹。

click 的介紹就告一段落,它將會是你編寫命令列程式的一大利器。在下一篇文章中,我們依然會通過實現一個簡單的 git 程式來進行 click 的實戰。


『講解開源專案系列』——讓對開源專案感興趣的人不再畏懼、讓開源專案的發起者不再孤單。跟著我們的文章,你會發現程式設計的樂趣、使用和發現參與開源專案如此簡單。歡迎留言聯絡我們、加入我們,讓更多人愛上開源、貢獻開源