1. 程式人生 > >命令列選項與引數解析(轉)

命令列選項與引數解析(轉)

argparse模組作為optparse的一個替代被新增到Python2.7。argparse的實現支援一些不易於新增到optparse以及要求向後不相容API變化的特性,因此以一個新模組新增到標準庫。

與optparse相比較

argparse的API類似於optparse,甚至在很多情況下通過更新所使用的類名和方法名,使用argparse作為一個簡單的替代。然而,有些地方在新增新特性時不能保持直接相容性。

你必須視情況決定是否升級已有的程式。如果你已編寫了額外的程式碼以彌補optparse的侷限,也許你想升級程式以減少你需要維護的程式碼量。若argparse在所有部署平臺上都可用,那麼新的程式應儘可能使用argparse。

設定一個解析器

使用argparse的第一步就是建立一個解析器物件,並告訴它將會有些什麼引數。那麼當你的程式執行時,該解析器就可以用於處理命令列引數。

解析器類是 ArgumentParser 。構造方法接收幾個引數來設定用於程式幫助文字的描述資訊以及其他全域性的行為或設定。

import argparse
parser = argparse.ArgumentParser(description='This is a PyMOTW sample program')

定義引數

argparse是一個全面的引數處理庫。引數可以觸發不同的動作,動作由 add_argument()

方法的 action 引數指定。 支援的動作包括儲存引數(逐個地,或者作為列表的一部分),當解析到某引數時儲存一個常量值(包括對布林開關真/假值的特殊處理),統計某個引數出現的次數,以及呼叫一個回撥函式。

預設的動作是儲存引數值。在這種情況下,如果提供一個型別,那麼在儲存之前會先把該引數值轉換成該型別。如果提供 dest 引數,引數值就儲存為命令列引數解析時返回的名稱空間物件中名為該 dest 引數值的一個屬性。

解析一個命令列

定義了所有引數之後,你就可以給 parse_args() 傳遞一組引數字串來解析命令列。預設情況下,引數是從 sys.argv[1:] 中獲取,但你也可以傳遞自己的引數列表。選項是使用GNU/POSIX語法來處理的,所以在序列中選項和引數值可以混合。

parse_args() 的返回值是一個名稱空間,包含傳遞給命令的引數。該物件將引數儲存其屬性,因此如果你的引數 dest"myoption",那麼你就可以args.myoption 來訪問該值。

簡單示例

以下簡單示例帶有3個不同的選項:一個布林選項(-a),一個簡單的字串選項(-b),以及一個整數選項(-c)。

import argparse

parser = argparse.ArgumentParser(description='Short sample app')

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print parser.parse_args(['-a', '-bval', '-c', '3'])

有幾種方式傳遞值給單字元選項。以上例子使用了兩種不同的形式,-bval-c val

$ python argparse_short.py
Namespace(a=True, b='val', c=3)

在輸出中與'c'關聯的值是一個整數,因為程式告訴ArgumentParser在儲存之前先轉換該引數。

“長”選項名字,即選項的名字多於一個字元,以相同的方式進行處理。

import argparse

parser = argparse.ArgumentParser(description='Example with long option names')

parser.add_argument('--noarg', action="store_true", default=False)
parser.add_argument('--witharg', action="store", dest="witharg")
parser.add_argument('--witharg2', action="store", dest="witharg2", type=int)

print parser.parse_args(['--noarg', '--witharg', 'val', '--withargs=3'])

結果也類似:

$ python argparse_long.py
Namespace(noarg=True, witharg='val', witharg2=3)

argparse區別於optparse的一個地方是對非選項引數值的處理。optparse只進行選項解析,而argparse是一個全面的命令列引數解析工具,也處理非選項引數。

import argparse

parser = argparse.ArgumentParser(description='Example with non-optional arguments')

parser.add_argument('count', action="store", type=int)
parser.add_argument('units', action="store")

print parser.parse_args()

在這個例子中,“count”引數是一個整數,“units”引數儲存為一個字串。其中任意一個引數若沒有在命令列中提供,或給定的值不能被轉換為正確的型別,就會報告一個錯誤。

$ python argparse_arguments.py 3 inches

Namespace(count=3, units='inches')

$ python argparse_arguments.py some inches

usage: argparse_arguments.py [-h] count units
argparse_arguments.py: error: argument count: invalid int value: 'some'

$ python argparse_arguments.py

usage: argparse_arguments.py [-h] count units
argparse_arguments.py: error: too few arguments

引數動作

argparse內建6種動作可以在解析到一個引數時進行觸發:

store 儲存引數值,可能會先將引數值轉換成另一個數據型別。若沒有顯式指定動作,則預設為該動作。

store_const 儲存一個被定義為引數規格一部分的值,而不是一個來自引數解析而來的值。這通常用於實現非布林值的命令列標記。

store_ture/store_false 儲存相應的布林值。這兩個動作被用於實現布林開關。

append 將值儲存到一個列表中。若引數重複出現,則儲存多個值。

append_const 將一個定義在引數規格中的值儲存到一個列表中。

version 列印關於程式的版本資訊,然後退出

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-s', action='store', dest='simple_value',
        help='Store a simple value')

parser.add_argument('-c', action='store_const', dest='constant_value',
        const='value-to-store',
        help='Store a constant value')

parser.add_argument('-t', action='store_true', default=False,
        dest='boolean_switch',
        help='Set a switch to true')
parser.add_argument('-f', action='store_false', default=False,
        dest='boolean_switch',
        help='Set a switch to false')

parser.add_argument('-a', action='append', dest='collection',
        default=[],
        help='Add repeated values to a list')

parser.add_argument('-A', action='append_const', dest='const_collection',
        const='value-1-to-append',
        default=[],
        help='Add different values to list')
parser.add_argument('-B', action='append_const', dest='const_collection',
        const='value-2-to-append',
        help='Add different values to list')

parser.add_argument('--version', action='version', version='%(prog)s 1.0')

results = parser.parse_args()
print 'simple_value     =', results.simple_value
print 'constant_value   =', results.constant_value
print 'boolean_switch   =', results.boolean_switch
print 'collection       =', results.collection
print 'const_collection =', results.const_collection
$ python argparse_action.py -h

usage: argparse_action.py [-h] [-s SIMPLE_VALUE] [-c] [-t] [-f]
                          [-a COLLECTION] [-A] [-B] [--version]

optional arguments:
  -h, --help       show this help message and exit
  -s SIMPLE_VALUE  Store a simple value
  -c               Store a constant value
  -t               Set a switch to true
  -f               Set a switch to false
  -a COLLECTION    Add repeated values to a list
  -A               Add different values to list
  -B               Add different values to list
  --version        show program's version number and exit

$ python argparse_action.py -s value

simple_value     = value
constant_value   = None
boolean_switch   = False
collection       = []
const_collection = []

$ python argparse_action.py -c

simple_value     = None
constant_value   = value-to-store
boolean_switch   = False
collection       = []
const_collection = []

$ python argparse_action.py -t

simple_value     = None
constant_value   = None
boolean_switch   = True
collection       = []
const_collection = []

$ python argparse_action.py -f

simple_value     = None
constant_value   = None
boolean_switch   = False
collection       = []
const_collection = []

$ python argparse_action.py -a one -a two -a three

simple_value     = None
constant_value   = None
boolean_switch   = False
collection       = ['one', 'two', 'three']
const_collection = []

$ python argparse_action.py -B -A

simple_value     = None
constant_value   = None
boolean_switch   = False
collection       = []
const_collection = ['value-2-to-append', 'value-1-to-append']

$ python argparse_action.py --version

argparse_action.py 1.0

選項字首

argparse選項的預設語法是基於Unix約定的,使用一個“-”字首來表示命令列開關。argparse支援其他字首,因此你可以使得你的程式遵照本地平臺的預設語法(例如,在Window上使用“/”)或者遵循不同的約定。

import argparse

parser = argparse.ArgumentParser(description='Change the option prefix charaters', 
        prefix_chars='-+/')

parser.add_argument('-a', action="store_false", default=None,
        help='Turn A off')

parser.add_argument('+a', action="store_true", default=None,
        help='Turn A on')

parser.add_argument('//noarg', '++noarg', action="store_true", default=False)

print parser.parse_args()

ArgumentParser 方法的prefix_chars 引數設定為一個字串,該字串包含所有允許用來表示選項的字元。需要理解的是雖然prefix_chars包含允許用於開關的字元,但單個引數定義只能使用一種給定的開關語法。這讓你可以對使用不同字首的選項是否是別名(比如獨立於平臺的命令列語法的情況)或替代選擇(例如,使用“+”表明開啟一個開發,“-”則為關閉一個開關)進行顯式地控制。在上述例子中,+a-a是不同的引數,//noarg 也可以 ++noarg 提供,但不是 --noarg

$ python argparse_prefix_chars.py -h

usage: argparse_prefix_chars.py [-h] [-a] [+a] [//noarg]

Change the option prefix characters

optional arguments
    -h, --help  show this help message and exit
    -a  Turn A off
    +a  Turn A on
    //noarg,++noarg

$ python argparse_prefix_chars.py +a

Namespace(a=True, noarg=False)

$ python argparse_prefix_chars.py -a

Namespace(a=False, noarg=False)

$ python argparse_prefix_chars.py //noarg

Namespace(a=None, noarg=True)

$ python argparse_prefix_chars.py ++noarg

Namespace(a=None, noarg=True)

$ python argparse_prefix_chars.py --noarg

usage: argparse_prefix_chars.py [-h] [-a] [+a] [//noarg]
argparse_prefix_chars.py: error: unrecognized arguments: --noarg

引數來源

目前為止所見的例子中,提供給解析器的引數列表來自於顯式傳遞的一個列表,或隱式地從sys.argv獲取的。顯式傳遞列表在你使用argparse來處理類命令列但並不是來自命令列(比如來自一個配置檔案)的指令之時比較有用。

import argparse
from ConfigParser import ConfigParser
import shlex

parser = argparse./span>ArgumentParser(description='Short sample app')

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

config = ConfigParser()
config.read('argparse_witH_shlex.ini')
config_value = config.get('cli', 'options')
print 'Config: ', config_value

argument_list = shlex.split(config_value)
print 'Arg List:', argument_list

print 'Results:', parser.parse_args(argument_list)

shlex使得切分儲存在配置檔案中的字串非常容易。

$ python argparse_with_shlex.py

Config: -a -b 2
Arg List: ['-a', '-b', '2']
Results: Namespace(a=True, b='2', c=None)

另一種自己處理配置檔案的方法是使用fromfile_prefix_chars指定一個包含一組要待處理引數的輸入檔案來告訴argparse怎樣識別引數。

import argparse
from ConfigParser import ConfigParser
import shlex

parser = argparse.ArgumentParser(description='Short sample app',
        fromfile_prefix_chars='@'
        )

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print parser.parse_args(['@argparse_fromfile_prefix_chars.txt'])

該示例程式碼在找到一個以@為字首的引數時即停止往下讀取,然後從以該引數命名的檔案中查詢更多的引數。例如,輸入檔案argparse_fromfile_prefix_chars.txt包含一系列引數,一行一個:

-a
-b
2

那麼處理該檔案產生的輸出為:

$ python argparse_fromfile_prefix_chars.py

Namespace(a=True, b='2', c=None)

自動生成選項

經過配置argparse會自動新增選項用來生成幫助資訊以及為你的應用程式顯示版本資訊。

ArgumentParser的引數add_help 控制幫助資訊相關的選項。

import argparse

parser = argparse.ArgumentParser(add_help=True)

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print parser.parse_args()

幫助選項(-h和–help)預設是新增的,但可以通過將add_help設定為false來禁用。

import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print parser.parse_args()

雖然-h--help是事實上的請求幫助的標準選項名稱,但一些應用或argparse的使用要麼不需要提供幫助要麼需要將這兩個選項名稱用於其他目標。

$ python argparse_with_help.py -h

usage: argparse_with_help.py [-h] [-a] [-b B] [-c C]

optional arguments:
    -h, --help  show this help message and exit
    -a
    -b B
    -c C

$ python argparse_without_help.py -h

usage: argparse_without_help.py [-a] [-b B] [-c C]
argparse_without_help.py: error: unrecognized arguments: -h

當在ArgumentParser構造方法設定版本後,就會新增版本選項(-v--version)。

import argparse

parser = argparse.ArgumentParser(version='1.0')

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print parser.parse_args()

print 'This is not printed'

兩種形式的選項愛那個都會列印程式的版本字串,然後立即退出程式。

$ python argparse_with_version.py -h

usage: argparse_with_version.py [-h] [-v] [-a] [-b B] [-c C]

optional arguments:
    -h, --help  show this help message and exit
    -v, --version   show program's version number and exit
    -a
    -b B
    -c C

$ python argparse_with_version.py -v

1.0

解析器組

argparse包含若干特性用於組織你的引數解析器,使得實現更為簡單,也能提高輸出幫助資訊的可用性。

共享解析器規則

我們常常需要實現一套命令列程式,這些程式都帶一組引數,只是在某些方面有特殊化。例如,如果所有程式都需要在使用者進行任何實際的操作之前對使用者進行認證,那麼它們就都需要支援--user--password選項。你可以共享的選項來定義一個“父母”解析器,然後令單個程式的解析器從該“父母”解析器繼承共享選項,這樣就不必顯式為每個ArgumentParser新增共享選項。

第一步是以共享的引數定義建立“父母”解析器。由於“父母”解析器的後代使用者會新增相同的幫助選項,從而會引發一個異常,所以在基礎解析器中我們關閉自動幫助選項生成。

import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('--user', action="store")
parser.add_argument('--password', action="store")

接下來,以父母解析器集建立另一個解析器:

import argparse
import argparse_parent_base

parser = argparse.ArgumentParser(parents=[argparse_parent_base.parser])

parser.add_argument('--local-arg', action="store_true", default=False)

print parser.parse_args()

得到的程式帶有三個選項:

$ python argparse_uses_parent.py -h

usage: argparse_uses_parent.py [-h] [--user USER] [--password PASSWORD]
                           [--local-arg]

optional arguments:
    -h, --help           show this help message and exit
    --user USER
    --password PASSWORD
    --local-arg

衝突的選項

前一個例子指出以相同的引數名字為一個解析器新增兩個引數處理器會引發一個異常。可以通過傳遞一個conflict_handler來改變衝突消除行為。argparse有兩個內建的衝突處理器error(預設)和resolveresolve會基於衝突選項的新增順序來選擇一個引數處理器。

import argparse

parser = argparse.ArgumentParser(conflict_handler='resolve')

parser.add_argument('-a', action="store")
parser.add_argument('-b', action="store", help="Short alone")
parser.add_argument('--long-b', '-b', action="store", help="Long and short together")

print parser.parse_args(['-h'])

由於最後一個處理器所給定的引數名已被使用,那麼本例中獨立選項-b將被--long-b的別名所覆蓋。

$ python argparse_conflict_handler_resolve.py

usage: argparse_conflict_handler_resolve.py [-h] [-a A] [--long-b LONG_B]

optional arguments:
    -h, --help  show this help message and exit
    -a A
    --long-b LONG_B, -b LONG_B
            Long and short together

切換add_argument()的呼叫順序就可以啟用獨立的選項:

import argparse

parser = argparse.ArgumentParser(conflict_handler='resolve')

parser.add_argument('-a', action="store")
parser.add_argument('--long-b', '-b', action="store", help='Long and short together')
parser.add_argument('-b', action="store", help='Short alone')

print parser.parse_args([-h])

現在兩個選項可以一起使用了。

$ python argparse_conflict_handler_resolve2.py

usage: argparse_conflict_handler_resolve2.py [-h] [-a A] [--long-b LONG_B] [-b B]

optional arguments:
    -h, --help  show this help message and exit
    -a A
    --long-b LONG_B Long and short together
    -b B    Short alone

引數群組

argparse能將引數定義組合成“群組”。預設情況下是使用兩個群組,一個是選項的群組,另一個是必須的與位置相關的引數群組。

import argparse

parser = argparse.ArgumentParser(description='Short sample app')

parser.add_argument('--optional', action="store_true", default=False)
parser.add_argument('positional', action="store")

print parser.parse_args()

群組在輸出的幫助資訊中顯示為分開的“與位置相關的引數”和“可選引數”兩個部分:

$ python argparse_default_grouping.py

usage: argparse_default_grouping.py [-h] [--optional] positional

Short sample app

positional arguments:
    positional

optional arguments:
    -h, --help  show this help message and exit
    --optional

你可以調整群組來提高幫助資訊中群組的邏輯性,這樣相關選項或值能記錄在一起。可以使用自定義群組來重寫之前的共享選項的示例,如此在幫助資訊中身份認證的選項就可以顯示在一起。

在基礎解析器中使用add_argument_group()來建立一個“身份認證”群組,然後逐個新增身份認證相關的選項到該群組。

import argparse

parser = argparser.ArgumentParser(add_help=False)

group = parser.add_argument_group('authentication')

group.add_argument('--user', action="store")
group.add_argument('--password', action="store")

與之前一樣,程式使用基於群組的父母解析器列表作為parents的值。

import argparse
import argparse_parent_with_group

parser = argparse.ArgumentParser(parents=[argparse_parent_with_group.parser])

parser.add_argument('--local-arg', action="store_true", default=False)

print parser.parse_args()

現在輸出的幫助資訊一起顯示身份認證選項。

$ python argparse_uses_parent_with_group.py -h

usage: argparse_uses_parent_with_group.py [-h] [--user USER] [--password PASSWORD] [--local-arg]

optional arguments:
    -h, --help  show this message and exit
    --local-arg

authentication:
    --user USER
    --password PASSWORD

互斥選項

定義互斥的選項是選項分組特性的一個特例,使用add_mutually_exclusive_group()而不是add_argument_group()

import argparse

parser = argparse.ArgumentParser()

group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action="store_true")

print parser.parse_args()

argparse會為你強制執行互斥性,因此一次使用僅能給出該群組的選項中的一個。

$ python argparse_mutually_exclusive.py -h

usage: argparse_mutually_exclusive.py [-h] [-a | -b]

optional arguments:
    -h, --help  show this help message and exit
    -a
    -b

$ python argparse_mutually_exclusive.py -a

Namespace(a=True, b=False)

$ python argparse_mutually_exclusive.py -b

Namespace(a=False, b=True)

$ python argparse_mutually_exclusive.py -a -b

usage: argparse_mutually_exclusive.py [-h] [-a | -b]
argparse_mutually_exclusive.py: error: argument -b: not allowed with argument -a

巢狀解析器

上述的父母解析器方式是在相關命令之間共享選項的方式之一。另一種方式是將多個命令組合進一個程式中,使用子解析器來處理命令列的每個部分。就像svnhg,以及其他帶有多個命令列行為或子命令的程式那樣。

一個用於處理檔案系統目錄的程式可能會像這樣定義命令用於建立、刪除、以及列出一個目錄的內容:

import argparse

parser = argparse.ArgumentParser()

subparsers = parser.add_subparsers(help='commands')

# A list command
list_parser = subparsers.add_parser('list', help='List contents')
list_parser.add_argument('dirname', action='store', help='Directory to list')

# A create command
create_parser = subparsers.add_parser('create', help='Create a directory')
create_parser.add_argument('dirname', action='store', help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
        help='Set permissions to prevent writing to the directory')

# A delete command
delete_parser = subparsers.add_parser('delete', help='Remove a directory')
delete_parser.add_argument('dirname', action='store', help='The directory to remove')
delete_parser.add_argument('--recursive', '-r', default=False, action='store_true',
        help='Remove the contents of the directory, too')

print parser.parse_args()

輸出的幫助資訊顯示作為“命令”的命名子解析器能夠在命令列中作為位置引數進行指定。

$ python argparse_subparsers.py -h

usage: argparse_subparsers.py [-h] {list, create, delete} ...

positional arguments:
    {list, create, delete} commands
        list    List contents
        create  Create a directory
        delete  Remove a directory

optional arguments:
    -h, --help  show this help message and exit

每個子解析器也有自己的幫助資訊,描述那個命令的引數和選項。

$ python argparse_subparsers.py create -h

usage: argparse_subparsers.py create [-h] [--read-only] dirname

positional arguments:
    dirname New directory to create

optional arguments:
    -h, --help  show this help message and exit
    --read-only Set permissions to prevent writing to the directory