2014-03-05 62 views
2

我正在寫一個我想要使用Setuptools分發的腳本。我已將此腳本添加到我的setup.py中的entry_points部分。在Setuptools中使用argparse entry_points

從setuptools的文檔:

您指定的函數被稱爲不帶參數,它們的返回值傳遞給sys.exit(),這樣你就可以返回一個錯誤級別或信息打印到stderr

由於該方法將返回而不是退出它變得更加可測試。出於可測性的目的,我接受的方法默認爲sys.argv。到現在爲止還挺好。

將argparse添加到組合中時出現問題。當argparse不能解析參數時,它會調用sys.exit。現在我真的更喜歡argparse不這樣做,因爲這是由setuptools包裝器處理的。我能想到的解決這個問題的第一件事是重寫argparse.ArgumentParser但後來我看到了這一點:

# =============== 
# Exiting methods 
# =============== 
def exit(self, status=0, message=None): 
    if message: 
     self._print_message(message, _sys.stderr) 
    _sys.exit(status) 

def error(self, message): 
    """error(message: string) 

    Prints a usage message incorporating the message to stderr and 
    exits. 

    If you override this in a subclass, it should not return -- it 
    should either exit or raise an exception. 
    """ 
    self.print_usage(_sys.stderr) 
    self.exit(2, _('%s: error: %s\n') % (self.prog, message)) 

所以文檔字符串狀態我不應該返回,並拋出一個異常堅持。我應該如何解決這個問題?

主要方法,如果我沒有解釋不夠徹底:

def main(args=sys.argv): 
    parser = ArgumentParser(prog='spam') 

    # parser is configured here 

    parsed = parser.parse_args(args) 

    # Parsed args are used here 

回答

1

你不從errorreturn的原因是,解析器將繼續解析。一些錯誤會在最後出現(例如關於未解析的字符串),但其他錯誤可能會提前發生(例如,第一個參數字符串的錯誤類型)。如果從錯誤方法返回,parse_args的行爲是不可預知的。通常你想讓解析器退出並返回控制你的代碼。

你想要做的是將parse_args()調用包裝在try: except SystemExit:塊中。我經常使用的測試腳本是這樣的:

for test in ['-o FILE', 
    ... 
     ]: 
    print(test) 
    try: 
     print(parser.parse_args(test.split())) 
    except SystemExit: 
     pass 

你可以使用error和/或exit返回其他類型的異常。他們也可以繞過使用信息。但以某種方式,您需要在包裝中捕獲異常。

+0

追趕'SystemError'將不會有任何好處,因爲'SystemExit'不是它的子類。我認爲這是一個錯字。 ;-)我決定在我的主要方法中捕獲'SystemExit',這又會返回狀態碼。這並沒有擺脫我在測試中遇到的麻煩,所以我必須不斷修補'sys.stdout'和朋友以查看所寫的內容。擴展'argparse'是一件痛苦的事情,因爲它很大程度上沒有記錄,並且應用了工廠模式。提出例外的意義非常明顯,我不應該問這個問題。感謝您的回答。 ;-) – siebz0r

+0

是的,SysExit是正確的。 test_argparse.py將stdout和stderr重定向到它的ErrorRaisingArgumentParser類中。 – hpaulj

0

如果您正在開始一個新項目或有時間進行一些重構,那麼您可以考慮使用Click庫。點擊既有setuptools integration和「testability」爲特徵,其他方面的考慮之中 - 這裏是從文檔的例子測試片段,都創建了一個小型的命令行界面,然後立即測試它:

import click 
from click.testing import CliRunner 

def test_hello_world(): 
    @click.command() 
    @click.argument('name') 
    def hello(name): 
     click.echo('Hello %s!' % name) 

    runner = CliRunner() 
    result = runner.invoke(hello, ['Peter']) 
    assert result.exit_code == 0 
    assert result.output == 'Hello Peter!\n'