使用argparse指定文件扩展名

时间:2014-01-05 13:59:57

标签: python argparse file-extension

我想用argparse指定几个文件扩展名。

我已经尝试过以下代码,但它不起作用。如何使用argparse指定多个文件扩展名?

parser.add_argument('file', action = 'store', type = argparse.FileType('r'), choices=["*.aaa", "*.bbb"])

编辑:我找到了自己的解决方案,使用字符串类型而不是FileType:

def input_ok(string):
    if not os.path.exists(string):
        raise argparse.ArgumentTypeError("Filename %r doesn\'t exists in this directory." % string)

    if string[-4:] != ".aaa" and string[-4:] != ".bbb":
        raise argparse.ArgumentTypeError("%r is not a .aaa or a .bbb file." % string)
return string

...

parser.add_argument('input_path', action = 'store',
    type = input_ok, #argparse.FileType('r'), #choices=["*.stl", "*.csv"])

2 个答案:

答案 0 :(得分:3)

问题的症结在于choices的工作原理。 Argparse首先构建传递参数的列表并进行类型转换,然后检查是否包含在in运算符的选项中。这意味着它不会进行任何模式匹配(匹配'*.aaa'),但会检查字符串是否相等。相反,我们可以让自己的容器传递给选择。

不使用argparse.Filetype,它看起来像这样。 Argparse还需要容器__iter__来为帮助消息创建Metavar元组。

class Choices():
    def __init__(self, *choices):
        self.choices = choices

    def __contains__(self, choice):
        # True if choice ends with one of self.choices
        return any(choice.endswith(c) for c in self.choices)

    def __iter__(self):
        return iter(self.choices)

parser.add_argument('file', action='store', choices=Choices('.aaa', '.bbb'))

您可以通过更改__contains__来满足您的需求,从而扩展此想法。例如,如果您还传递了type=argparse.FileType('r'),那么在检查包含之前,argparse将转换为文件对象。

def __contains__(self, choice):
    # choice is now a file object
    return any(choice.name.endswith(c) for c in self.choices)

顺便说一句,这就是我讨厌argparse的原因。它过于复杂,并试图做更多的事情。我不认为验证应该在参数解析器中完成。使用docopt并自行验证。

答案 1 :(得分:1)

argparse接受字符串并没有任何问题,之后你自己进行验证。有时你想检查一个文件名是否正确,但是以后再打开它(例如使用with open(filename) as f:)。这可能是最简单的方法。

kalhartt's Choices类的替代方法是使用os.pathglob来获取允许文件列表。

p.add_argument('file',choices=glob.glob('*.txt'))
In [91]: p.parse_args('test.txt'.split())
Out[91]: Namespace(file='test.txt')

问题在于帮助和错误消息可能过长,列出了所有允许的文件名。

choicesFileType不兼容。那是因为它在文件打开后对选择进行测试

p.add_argument('file',choices=[open('test.txt')],type=argparse.FileType('r'))
p.parse_args('test.txt'.split())
# usage: python [-h] {<open file 'test.txt', mode 'r' at 0xa102f98>}
# error: argument file: invalid choice: <open file 'test.txt', mode 'r' at 0xa102f40>
# (choose from <open file 'test.txt', mode 'r' at 0xa102f98>)

即使文件名相同,两个打开文件的ID也不一样。正如kalhartt's示例所示,choices对象必须具有自定义__contains__功能(用于测试文件名的功能,例如f.name.endswith('txt'))。

但是如果你真的喜欢FileType打开文件的事实,我可以想象它的子类化,所以它会检查扩展。

class FileTypeWithExtension(argparse.FileType):
    def __init__(self, mode='r', bufsize=-1, extension=None):
        self._extension = extension
        super(FileTypeWithExtension, self).__init__()
    def __call__(self, string):
        if string != '-' and self._extension:
            if not string.endswith(self._extension):
               # just testing against one extension for now
               raise argparse.ArgumentTypeError('wrong extension')
        return super(FileTypeWithExtension, self).__call__(string)

p.add_argument('file',type=FileTypeWithExtension('r',extension='txt'))
p.parse_args('test.tst'.split())
#usage: ipython [-h] file
#ipython: error: argument file: wrong extension

p.parse_args('test.txt'.split())
# Namespace(file=<open file 'test.txt', mode 'r' at 0xa13ce90>)