我想用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"])
答案 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.path
或glob
来获取允许文件列表。
p.add_argument('file',choices=glob.glob('*.txt'))
In [91]: p.parse_args('test.txt'.split())
Out[91]: Namespace(file='test.txt')
问题在于帮助和错误消息可能过长,列出了所有允许的文件名。
此choices
与FileType
不兼容。那是因为它在文件打开后对选择进行测试
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>)