2015-10-19 69 views
2

(這是一個簡化的問題。)我正在編寫一個涉及Python組件的API。這些可能是功能,但具體來說,讓我們說他們是對象。我希望能夠從命令行解析各種組件的選項。使用Python/argparse創建可組合/分層命令行分析器

from argparse import ArgumentParser 

class Foo(object): 
    def __init__(self, foo_options): 
     """do stuff with options""" 

    """...""" 

class Bar(object): 
    def __init__(sef, bar_options): 
     """...""" 

def foo_parser(): 
    """(could also be a Foo method)""" 
    p = ArgumentParser() 
    p.add_argument('--option1') 
    #... 
    return p 

def bar_parser(): "..." 

但現在我希望能夠建立更大的組件:

def larger_component(options): 
    f1 = Foo(options.foo1) 
    f2 = Foo(options.foo2) 
    b = Bar(options.bar) 
    # ... do stuff with these pieces 

精細。但是如何編寫適當的解析器?我們不妨這樣的事情:

def larger_parser(): # probably need to take some prefix/ns arguments 
    # general options to be overridden by p1, p2 
    # (this could be done automagically or by hand in `larger_component`): 
    p = foo_parser(prefix=None,   namespace='foo') 
    p1 = foo_parser(prefix='first-foo-', namespace='foo1') 
    p2 = foo_parser(prefix='second-foo-', namespace='foo2') 
    b = bar_parser() 
    # (you wouldn't actually specify the prefix/namespace twice:) 
    return combine_parsers([(p1, namespace='foo1', prefix='first-foo-'), 
          (p2,...),p,b]) 

larger_component(larger_parser().parse_args()) 
# CLI should accept --foo1-option1, --foo2-option1, --option1 (*) 

這看起來有點像​​的parents功能,如果你忘了我們想要的前綴(以便能夠添加相同類型的多個解析器) 和可能命名空間(以便我們可以構建樹形結構的命名空間以反映組件的結構)。

當然,我們希望large_component和larger_parser以相同的方式組合,並且傳遞給某個組件的名稱空間對象應始終具有相同的內部形狀/命名結構。

麻煩似乎是在​​API基本上是關於變異的你分析器,但查詢它們是比較困難的 - 如果你直接打開一個 數據類型爲解析器,你可以只走這些對象。如果用戶編寫了一堆函數以手動添加參數給解析器,但是每個add_argument調用都必須帶一個前綴,並且整個事情變得非常難以理解,並且可能不可組合,所以我設法破解了某些有些作用。 (你可以通過複製內部數據結構的某些部分來對此進行抽象...)。我也試圖子類parsergroup對象...

你可以想象這可能使用更多的代數CLI-解析API是可能的,但我不認爲重寫​​這裏是一個很好的解決方案。

有沒有一種已知的/直接的方法來做到這一點?

+0

你能證明你想使用一個命令行的一個例子?起初,它看起來像你想'subparsers'(這是argparse默認提供的),但我不能100%確定它是否適合你的需求。 – mgilson

+0

'subparsers'似乎是關於解析器(解析器A,或B或...)的「總和」,而我想要一個「產品」(所有選項可同時使用)。 – Fixnum

+0

有關示例,請參閱問題中用'(*)'指示的行。 – Fixnum

回答

1

一些想法,可以幫助您構成更大的解析器:

parser = argparse.ArgumentParser(...) 
arg1 = parser.add_argument('--foo',...) 

現在arg1是由add_argument創建的Action對象的引用。我建議在一個交互式shell中做這件事,看看它的屬性。或者至少打印其repr。您也可以嘗試修改屬性。 解析器「知道」有關參數的大部分內容都包含在這些actions中。從某種意義上來說,解析器是一個「包含」一堆「動作」的對象。

也看在:

parser._actions 

這是行動,其中將包括默認的幫助,以及您添加的那些解析器的主列表。

parents機制將父母引用的Action副本複製到孩子。請注意,它不會製作Action對象的副本。它還重新創建了論證組 - 但這些組僅僅用於對幫助線進行分組。它們與解析無關。

args1, extras = parser.parse_known_args(argv, namespace) 

在處理多個解析器時非常有用。有了它,每個解析器都可以處理它所知道的參數,並將剩下的參數傳遞給其他人。嘗試理解該方法的輸入和輸出。

我們已經在之前的SO問題中討論過合成Namespace對象。默認的argparse.Namespace類是一個帶有repr方法的簡單對象類。解析器只是使用hasattr,getattrsetattr,試圖儘可能非特定。你可以構造一個更復雜的命名空間類。

argparse subcommands with nested namespaces

您還可以自定義Action類。這就是大部分值插入到名字空間的地方(儘管默認值是在別處設置的)。

IPython使用​​,既用於主呼叫,又用於內部用於magic命令。它構造了來自config文件的許多參數。因此,可以使用默認配置,自定義配置或通過命令行參數在最後時刻設置許多值。

0

您可能可以使用組合動作的概念來實現所需的功能。你可以建立一個修改的命名空間,DEST,等等,因爲你需要的操作,然後用它們組合:

def compose_actions(*actions): 
    """Compose many argparse actions into one callable action. 

    Args: 
     *actions: The actions to compose. 

    Returns: 
     argparse.Action: Composed action. 
    """ 
    class ComposableAction(argparse.Action): 
     def __call__(self, parser, namespace, values, option_string=None): 
      for action in actions: 
       action(option_string, self.dest).__call__(parser, 
                  namespace, 
                  values, 
                  option_string) 
    return ComposableAction 

見例如:https://gist.github.com/mnm364/edee068a5cebbfac43547b57b7c842f1