62

私のPythonスクリプトは、コマンドラインで渡されたディレクトリからファイルを読み取る必要があります。コマンドラインで渡されたディレクトリが存在し、読み取り可能であることを検証するためにargparseで使用されるreadable_dirタイプを以下のように定義しました。さらに、デフォルト値(以下の例では/ 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()
4

3 に答える 3

43

数か月前に、「パス引数」のパッチをPython標準ライブラリのメーリングリストに提出しました。

この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
于 2015-10-16T23:40:21.150 に答える
39

タイプの代わりにカスタムアクションを作成できます。

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作成したディレクトリが書き込み可能になることをすでに保証しています)、プログラムの終了時に削除するように登録します。

于 2012-07-10T14:41:19.163 に答える
12

スクリプトが有効なものなしでは機能しないlaunch_directory場合は、必須の引数にする必要があります。

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

ところで、argparse.ArgumentTypeErrorの代わりにExceptionを使用する必要がありますreadable_dir()

于 2012-07-10T14:54:21.090 に答える