2015-10-30 44 views
2

我在argparse中爲我的python項目實現了一個CustomAction。 CustomAction用於在命令行上指定任意數量的name=value對樣式參數,即nargs='*'如何在使用argparse的CustomAction時停止處理參數

class NameValueAction(argparse.Action): 
    """ CustomAction for argparse to be able to process name,value \ 
     pairs specified as command line arguments. Specified as 

     $ python runner.py --env=target_env --props name1=value1 name2=value2 module/ 
    """ 
    def __call__(self, parser, namespace, values, option_string=None): 
     for value in values: 
      n, v = value.split('=') 
      setattr(namespace, n, v) 

麻煩的是,有沒有辦法從處理module/說法,是在命令行上停止__call____call__方法如何在不消耗module/參數的情況下適當結束並允許它由runner.py進行處理?

PS:我已經嘗試退出最後一個參數不是name=value,但這不起作用,因爲模塊已經被使用,我不知道如何將它放回堆棧。

+0

解析器根據其「nargs」和任何後續動作的「nargs」分配動作獲取的「values」列表。與'optparse'相比,這一行爲不會剝離某些主列表中的值。圍繞不同的positionals,optionals和'nargs'混合來查看每個值有多少個值。 – hpaulj

+0

另請參閱http://stackoverflow.com/questions/33301000/custom-parsing-function-for-any-number-of-arguments-in-python-argparse。它從多個'--flag = key:value'字符串構建一個字典。 – hpaulj

回答

1

有沒有辦法*防止「模塊/」被消耗,因爲它沒有關聯,表明它是一個單獨的參數,而不是由--props消耗name or flags

我假設你已經安裝--props爲:

parser.add_argument('--props', nargs='*', action=NameValueAction) 

,這樣會消耗許多ARGS越好。你需要給-m--module選項來獲得argparse來單獨存儲'module /'。

否則,你可以把模塊的位置ARG parser.add_argument('module')並指定--props之前在命令行上:

parser.add_argument('--env') 
parser.add_argument('--props', nargs='*', action=NameValueAction) 
parser.add_argument('module') 

""" Usage: 
$ python runner.py --env=target_env module/ --props name1=value1 name2=value2 
or 
$ python runner.py module/ --env=target_env --props name1=value1 name2=value2 
""" 

這過程是:順便說一下

>>> parser.parse_args('--env=target_env module/ --props name1=value1 name2=value2'.split()) 
Namespace(env='target_env', module='module/', name1='value1', name2='value2', props=None) 

,使用現有的代碼和如果沒有上面提出的更改,您可以在命令行中指定module=module,它將像name=value對那樣處理:

>>> parser.parse_args('--env=target_env --props name1=value1 name2=value2 module=module/'.split()) 
Namespace(env='target_env', module='module/', name1='value1', name2='value2', props=None) 

*如果你真的不能把它作爲一個單獨的阿根廷,那麼你就必須要內NameValueAction處理。我在你修改了__call__爲:

def __call__(self, parser, namespace, values, option_string=None): 
    for value in values: 
     try: 
      n, v = value.split('=') 
      setattr(namespace, n, v) # better to put this in the else clause actually 
     except ValueError: # "need more than 1 value to unpack" 
          # raised when there's no '=' sign 
      setattr(namespace, 'module', value) 

>>> parser.parse_args('--env=target_env --props name1=value1 name2=value2 MOARmodules/'.split()) 
Namespace(env='target_env', module='MOARmodules/', name1='value1', name2='value2', props=None) 

當然,那缺點是剩餘行動是多麼複雜。上面實現的那個的行爲類似於action=store,並且僅將其應用於'module'


您也可以嘗試與附加價值,以sys.argv但考慮當你這樣做,多數民衆贊成被消耗試驗,也許會有意想不到的副作用會影響類似,爲什麼你不應該插入/從列表中刪除時迭代它。

+0

感謝您的詳細解釋!這個程序的早期行爲沒有' - props',模塊被指定爲沒有參數。 ' - props'的引入擾亂了和平,並且需要改變一大堆依賴的腳本。爲了保持向後兼容,我不想使用額外的標誌來解析'module'參數。另一個麻煩是'module'是一個可選的參數,很難被NameValueAction處理。在你指出在NameValueAction中處理這個之後,我想我已經找到了一種方法。見下文。 – tsps

+0

@tsps然後讓模塊成爲默認的位置參數,並在模塊之後指定'--props'。所以附加了新的東西,你可以處理模塊被指定與失蹤,因爲這將是唯一的位置參數。 – aneroid

+0

的確 - 這也是一個合理的解決方案! – tsps

1

經過@無線電的線索,看看NameValueAction內處理我通過​​模塊讀取以找到一種可能的方式。 Actions在​​中執行命令行解析。在​​下的Action在命令行的一部分被觸發到程序。​​保留由用戶定義的默認Actions(例如:store, store_true, const等)和CustomAction對象的列表。然後將它們循環並按順序對命令行的一部分進行處理以找到匹配項並構建對應於每個ActionNamespace。在每次迭代argparse.Action可能會發現在命令行不匹配任何由Action處理的那部分,並返回它們(在外地_UNRECOGNIZED_ARGS_ATTR這是由屬性的Namespace'_unrecognized_args'標識)返回給調用者

argparse.py#parse_known_args(..)

try: 
    namespace, args = self._parse_known_args(args, namespace) 
    if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): 
     args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) 
     delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) 
    return namespace, args 
except ArgumentError: 
    err = _sys.exc_info()[1] 
    self.error(str(err)) 

如上所示,如果發現任何無法識別的參數,它們將返回給調用者args。類別NameValueAction可以利用它來讓它們由後面的任何其他Actions或項目的(runner.py)模塊處理。因此,課改:

class NameValueAction(argparse.Action): 
    def __call__(self, parser, namespace, values, option_string=None): 
     for value in values: 
      try: 
       n, v = value.split('=') 
       setattr(namespace, n, v) 
      except ValueError: 
       # when input has ended without an option, probably at module name 
       setattr(namespace, '_unrecognized_args', values[values.index(value):]) 

所以CMD線的工作原理如下:

$ python runner.py --env=target_env --props name1=value1 name2=value2 module/

時提供額外選項--props後規定,​​將停止處理當前Action並重復着。所以下面也將工作

$ python runner.py --env=target_env --props name1=value1 name2=value2 --timeout=300 module/

+0

'_UNRECOGNIZED_ARGS_ATTR'很有用。如果你使用'parse_known_args',我期望在'extras'列表中看到'module /'。唯一定義的使用此屬性的Action類是'subparsers'。 – hpaulj

+0

我認爲你的解析方法的大綱需要一些改進。我會稍後嘗試添加一些內容。 – hpaulj

+0

我正在尋找類似['_unrecognized_args''](https://hg.python.org/cpython/file/2.7/Lib/argparse.py#l1732)的東西,或者基本上是一種機制來添加一些東西給'to-parse'隊列/列表。太糟糕了,它沒有_documented_或有一個暴露的方法來訪問和修改它。 – aneroid

2

我來試試你的最新的自定義操作:

In [34]: parser=argparse.ArgumentParser() 

In [35]: parser.add_argument('--env') 
In [36]: parser.add_argument('--props',nargs='*',action=NameValueAction) 
Out[36]: NameValueAction(option_strings=['--props'], dest='props', nargs='*', const=None, default=None, type=None, choices=None, help=None, metavar=None) 

我得到一個unrecognized arguments誤差parse_args。你的動作正確定義爲未知:

In [37]: args=parser.parse_args('--env=target_env --props name1=value1 name2=value2 module/'.split()) 
usage: ipython2.7 [-h] [--env ENV] [--props [PROPS [PROPS ...]]] 
ipython2.7: error: unrecognized arguments: module/ 
... 

隨着parse_known_args我可以看到指定參數和演員沒有錯誤消息:

In [38]: parser.parse_known_args('--env=target_env --props name1=value1 name2=value2 module/'.split()) 
Out[38]: 
(Namespace(env='target_env', name1='value1', name2='value2', props=None), 
['module/']) 

因此,所有的字符串後--props被傳遞作爲values該行動。它將值分配給名稱空間並返回。 parse_known_argsunrecognized值從名稱空間中取出並放入該extras列表中。現在


我將添加一個位置,希望它會採取module/字符串:

In [39]: parser.add_argument('foo') 

In [40]: parser.parse_known_args('--env=target_env --props name1=value1 name2=value2 module/'.split()) 
usage: ipython2.7 [-h] [--env ENV] [--props [PROPS [PROPS ...]]] foo 
ipython2.7: error: too few arguments 
... 

哎呀,不同的錯誤,甚至parse_known_args。問題在於'module /'仍然在給--props,給foo留下任何東西。 --props有一個* nargs,這意味着它會獲得所有符合條件的參數(沒有-)。將'module /'放在命名空間unknown沒有幫助。解析器不會重新評估此列表中的字符串。

我可以使用' - '來表示後面的所有字符串都是定位符。現在--props沒有收到或處理'模塊'。相反,在下一次處理定位點時,它會被foo消耗。

In [41]: parser.parse_known_args('--env=target_env --props name1=value1 name2=value2 -- module/'.split()) 
Out[41]: 
(Namespace(env='target_env', foo='module/', name1='value1', name2='value2', props=None), 
[]) 

另一種任選的,如可用於標記的「--props」參數的結尾「--env」:

In [42]: parser.parse_known_args('--props name1=value1 name2=value2 --env=target_env module/'.split()) 
Out[42]: 
(Namespace(env='target_env', foo='module/', name1='value1', name2='value2', props=None), 
[]) 

注意progs=None出現在命名空間。這是因爲解析器在解析開始時將所有Action默認值加載到名稱空間中。你可以使用default=argparse.SUPPRESS來防止這種情況。


解釋見的參數如何分配到「*」可選這個錯誤/問題,什麼可以做,以保留一些用於以下positionals:

http://bugs.python.org/issue9338 argparse optionals with nargs='?', '*' or '+' can't be followed by positionals

https://stackoverflow.com/a/33405960/901925是另一個最近的SO問題,涉及到一個常規的位置,後面跟着兩個'?' positionals。

正如我在評論中指出的,​​與optparse不同。我相信在optparse每個動作(或等效)消耗盡可能多的字符串,並留下其餘的行動。在​​中,單個操作無權訪問主列表(arg_strings)。這是解析器決定Action獲取的字符串數量。


argparse.py文件更多細節。這是對parse_args相關部分的總結。

_parse_known_args(self, arg_strings, namespace): 
    # arg_strings - master list of strings from sys.argv 
    start_index = 0 
    while start_index<amax: 
     # step through arg_strings processing postionals and optionals 
     consume_positionals() 
     start_index = next_option_string_index 
     start_index = consume_optional(start_index) 

consume_optional(start_index): # function local to _parse_known_args 
    ... 
    start = start_index + 1 
    arg_count = <fn of available arguments and nargs> 
    stop = start + arg_count 
    args = arg_strings[start:stop] 
    <action = CustomAction.__call__> 
    take_action(action, args, option_string) 
    return stop 

take_action(action, argument_strings, ...): # another local function 
    # argument_strings is a slice of arg_strings 
    argument_values = self._get_values(action, argument_strings) 
    # _get_values passes strings through the action.type function 
    action(self, namespace, argument_values, option_string) 
    # no return 

的淨效應是,你CustomAction.__call__會從主arg_strings列表的片段中所取得的values列表。它無法訪問arg_strings,也無法訪問該片的startstop。所以它不能改變字符串本身或任何後續動作的分配。


另一個想法是,讓你無法解析成self.dest值。

class NameValueAction(argparse.Action): 
    def __call__(self, parser, namespace, values, option_string=None): 
     extras = [] 
     for value in values: 
      try: 
       n, v = value.split('=') 
       setattr(namespace, n, v) 
      except ValueError: 
       extras.append(value) 
     if len(extras):   
      setattr(namespace, self.dest, extras) 

然後解析(不foo位置)會產生:

In [56]: parser.parse_args('--props name1=value1 p1 name2=value2 module/'.split()) 
Out[56]: Namespace(env=None, name1='value1', name2='value2', props=['p1', 'module/']) 

args.props現在包含['p1','module/'],字符串--props了,但無法解析爲n=v對。這些可以根據需要進行解析後重新定位。

+0

「我會嘗試您最新的自定義操作」 - 是指@tsps還是我? [這是我的PB](http://pastebin.com/kVfSnjQs)。 – aneroid

+0

我正在嘗試tsps的第二次嘗試,將值置於'無法識別'屬性中。它沒有幫助。 – hpaulj

0

(回答只是因爲我也需要「吃」了一些參數從列表中事先未知的,下面的解決方案是相當通用的。)

正如@hpaulj上面提到的,使用定位參數將無法正常工作沒有繼承ArgumentParser,因爲解析器只是將所有內容傳遞給Action,但是如果您只想分析選項並將非選項參數作爲列表返回(即將它們傳遞給不同的解析器),則以下工作(在Python 3上)。4至少):

#!/usr/bin/env python3 

import argparse 
import itertools 

class EatUnknown(argparse.Action): 
    def __init__(self, option_strings, dest, nargs=None, *args, **kwargs): 
     nargs = argparse.REMAINDER 
     super().__init__(option_strings, dest, nargs, *args, **kwargs) 

    def __call__(self, parser, namespace, values, option_string=None): 
     def all_opt_strings(parser): 
      nested = (x.option_strings for x in parser._actions 
         if x.option_strings) 
      return itertools.chain.from_iterable(nested) 

     all_opts = list(all_opt_strings(parser)) 

     eaten = [] 
     while len(values) > 0: 
      if values[0] in all_opts: 
       break 
      eaten.append(values.pop(0)) 
     setattr(namespace, self.dest, eaten) 

     _, extras = parser._parse_known_args(values, namespace) 
     try: 
      getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(extras) 
     except AttributeError: 
      setattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR, extras) 

parser = argparse.ArgumentParser() 
parser.add_argument("--foo", action="append") 
parser.add_argument('--eatme', action=EatUnknown) 
parser.add_argument('--eater', action=EatUnknown) 

print(parser.parse_known_args()) 

可生產

$ ./argparse_eater.py --foo 1 AAA --eater 2 --unk-opt 3 --foo 4 BBB --eatme 5 --another-unk --foo 6 CCC 
(Namespace(eater=['2', '--unk-opt', '3'], eatme=['5', '--another-unk'], foo=['1', '4', '6']), ['AAA', 'CCC', 'BBB']) 

此示例「吃掉」的任何非選擇以及未知的選項參數(其中不能使用nargs='*',證明的例子),儘管是不是allow_abbrev兼容。

這個想法是使用一個簡單的遞歸,這顯然作爲代碼是可重入的。可能不是最好的想法依賴,但使用_unrecognized_args並不好。

鑑於OP,這將適用於多個發生--props

相關問題