【问题标题】:directory path types with argparse带有 argparse 的目录路径类型
【发布时间】:2012-07-10 01:30:15
【问题描述】:

我的 python 脚本需要从命令行传递的目录中读取文件。我已经定义了一个 readable_dir 类型,如下所示与 argparse 一起使用,以验证在命令行上传递的目录是否存在且可读。 此外,还为目录参数指定了默认值(以下示例中的 /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()

【问题讨论】:

  • 有用的代码示例。提高应该raise argparse.ArgumentTypeError,否则,我正在挖掘 readable_dir 类型。

标签: python argparse


【解决方案1】:

几个月前我提交了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

【讨论】:

  • 这是非常好的代码。很遗憾你的补丁没有被接受。
  • 谢谢,@BjörnLindqvist。我不太确定为什么。它可能会受益于其他人回复它并提醒 stdlib 维护者它仍然“在那里”并且有用......
【解决方案2】:

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

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)

但这对我来说似乎有点可疑——如果没有给出目录,它会传递一个不可读的目录,这似乎违背了首先检查目录是否可访问的目的。

请注意,正如 cmets 中所指出的,
raise argparse.ArgumentError(self, ...) 可能比 argparse.ArgumentTypeError 更好。

编辑

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

【讨论】:

  • mgilson,我不得不将预期目录 = 值 [0] 更改为预期目录 = 值。没有这个,只有参数中的第一个字符被拾取。您的解决方案在传入显式参数时有效(因为在这些情况下未验证默认值)。但是,当没有传入任何参数时,默认值不会被验证,这是一个问题。
  • @cravoori -- 我认为values 是一个列表的原因。我想只有在指定nargs=... 时才会发生这种情况。无论如何,我不认为有任何方法可以哄 argparse 在解析参数后进行验证(这是您真正要求的)。你必须自己做。我已经更新了我的代码,以便始终有一个有效的目录供您使用,当您的程序退出时,该目录将被删除。 (命令行中指定的目录不会被删除)。
  • 请注意临时目录仅用作示例
  • 次要建议:使用raise argparse.ArgumentTypeError("message") 引发错误会导致堆栈跟踪。如果您使用raise argparse.ArgumentError(self, "message"),它会很好地格式化而没有堆栈跟踪。
  • @djova -- 是的,很有趣。
【解决方案3】:

如果您的脚本在没有有效的launch_directory 的情况下无法运行,则应将其设为强制参数:

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

顺便说一句,您应该在readable_dir() 中使用argparse.ArgumentTypeError 而不是Exception

【讨论】:

  • argparse.ArgumentError(self, "error string") 是最好的,如果您希望用户看到一个很好的错误消息而不是堆栈跟踪。欲了解更多信息,请参阅:stackoverflow.com/questions/9881933/…
  • @Skotch: readable_dir 定义了一个类型,所以ArgumentTypeError 在这里是合适的。我已经修正了错字:action -> type
  • J.F. Sebastian:我很确定这是我们正在讨论的自定义操作(参见上面 mgilson 给出的 readable_dir 的定义,它是从 argparse.Action 派生的)。将自定义 argparse 操作作为类型传递将不起作用(至少在我尝试时它没有)。
  • @Apteryx:阅读问题。 readable_dir 是一个函数。我的回答与 mgilson 的回答无关
  • J.F.塞巴斯蒂安:对不起,你是对的。现在我明白了,使用函数定义类型比使用自定义操作更有意义。谢谢!
猜你喜欢
  • 2016-12-14
  • 2014-02-15
  • 1970-01-01
  • 1970-01-01
  • 2012-12-13
  • 1970-01-01
  • 1970-01-01
  • 2014-04-16
  • 2021-03-22
相关资源
最近更新 更多