argparse的目录路径类型

时间:2012-07-10 14:29:51

标签: python argparse

我的python脚本需要从命令行传递的目录中读取文件。我已经定义了一个如下所示的readable_dir类型,用于与argparse一起验证在命令行上传递的目录是否存在且可读。 此外,还为directory参数指定了默认值(下例中的/ tmp / non_existent_dir)。 这里的问题是argparse在默认值上调用readable_dir(),即使在命令行中显式传入目录参数的情况下也是如此。这会导致脚本丢失,因为在命令行上显式传入目录的上下文中不存在缺省路径/ tmp / non_existent_dir。 我可以通过不指定默认值并强制使用此参数来解决这个问题,或者通过将验证推迟到脚本的后面,但这是一个任何人都知道的更优雅的解决方案?

#!/usr/bin/python
import argparse
import os

def readable_dir(prospective_dir):
  if not os.path.isdir(prospective_dir):
    raise Exception("readable_dir:{0} is not a valid path".format(prospective_dir))
  if os.access(prospective_dir, os.R_OK):
    return prospective_dir
  else:
    raise Exception("readable_dir:{0} is not a readable dir".format(prospective_dir))

parser = argparse.ArgumentParser(description='test', fromfile_prefix_chars="@")
parser.add_argument('-l', '--launch_directory', type=readable_dir, default='/tmp/non_existent_dir')
args = parser.parse_args()

3 个答案:

答案 0 :(得分:35)

您可以创建自定义操作而不是类型:

import argparse
import os
import tempfile
import shutil
import atexit

class readable_dir(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        prospective_dir=values
        if not os.path.isdir(prospective_dir):
            raise argparse.ArgumentTypeError("readable_dir:{0} is not a valid path".format(prospective_dir))
        if os.access(prospective_dir, os.R_OK):
            setattr(namespace,self.dest,prospective_dir)
        else:
            raise argparse.ArgumentTypeError("readable_dir:{0} is not a readable dir".format(prospective_dir))

ldir = tempfile.mkdtemp()
atexit.register(lambda dir=ldir: shutil.rmtree(ldir))

parser = argparse.ArgumentParser(description='test', fromfile_prefix_chars="@")
parser.add_argument('-l', '--launch_directory', action=readable_dir, default=ldir)
args = parser.parse_args()
print (args)

但这对我来说似乎有点可疑 - 如果没有给出目录,它会传递一个不可读的目录,这似乎无法检查目录是否可以访问。

请注意,正如评论中指出的那样,raise argparse.ArgumentError(self, ...)而不是argparse.ArgumentTypeError可能更好。

修改

据我所知,没有办法验证默认参数。我想argparse开发人员只是假设如果你提供默认值,那么它应该是有效的。这里最快速,最简单的方法是在解析参数后立即验证参数。看起来,你只是想让一个临时目录做一些工作。如果是这种情况,您可以使用tempfile模块获取一个新目录。我更新了上面的答案以反映这一点。我创建了一个临时目录,使用它作为默认参数(tempfile已经保证它创建的目录是可写的)然后我注册它以便在你的程序退出时被删除。

答案 1 :(得分:23)

几个月前我提交了a patch for "path arguments" to the Python standard library mailing list

使用此PathType类,您只需指定以下参数类型以匹配现有目录 - 其他任何内容都将显示错误消息:

type = PathType(exists=True, type='dir')

这里是代码,可以很容易地修改,以便需要特定的文件/目录权限:

from argparse import ArgumentTypeError as err
import os

class PathType(object):
    def __init__(self, exists=True, type='file', dash_ok=True):
        '''exists:
                True: a path that does exist
                False: a path that does not exist, in a valid parent directory
                None: don't care
           type: file, dir, symlink, None, or a function returning True for valid paths
                None: don't care
           dash_ok: whether to allow "-" as stdin/stdout'''

        assert exists in (True, False, None)
        assert type in ('file','dir','symlink',None) or hasattr(type,'__call__')

        self._exists = exists
        self._type = type
        self._dash_ok = dash_ok

    def __call__(self, string):
        if string=='-':
            # the special argument "-" means sys.std{in,out}
            if self._type == 'dir':
                raise err('standard input/output (-) not allowed as directory path')
            elif self._type == 'symlink':
                raise err('standard input/output (-) not allowed as symlink path')
            elif not self._dash_ok:
                raise err('standard input/output (-) not allowed')
        else:
            e = os.path.exists(string)
            if self._exists==True:
                if not e:
                    raise err("path does not exist: '%s'" % string)

                if self._type is None:
                    pass
                elif self._type=='file':
                    if not os.path.isfile(string):
                        raise err("path is not a file: '%s'" % string)
                elif self._type=='symlink':
                    if not os.path.symlink(string):
                        raise err("path is not a symlink: '%s'" % string)
                elif self._type=='dir':
                    if not os.path.isdir(string):
                        raise err("path is not a directory: '%s'" % string)
                elif not self._type(string):
                    raise err("path not valid: '%s'" % string)
            else:
                if self._exists==False and e:
                    raise err("path exists: '%s'" % string)

                p = os.path.dirname(os.path.normpath(string)) or '.'
                if not os.path.isdir(p):
                    raise err("parent path is not a directory: '%s'" % p)
                elif not os.path.exists(p):
                    raise err("parent directory does not exist: '%s'" % p)

        return string

答案 2 :(得分:13)

如果您的脚本在没有有效launch_directory的情况下无法工作,那么它应该成为强制性参数:

parser.add_argument('launch_directory', type=readable_dir)

顺便说一下,您应该在argparse.ArgumentTypeError中使用Exception代替readable_dir()