web-dev-qa-db-ja.com

argparseを使用したディレクトリパスタイプ

My 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()
55
iruvar

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

_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_。

[〜#〜] edit [〜#〜]

私の知る限り、デフォルトの引数を検証する方法はありません。 argparse開発者は、デフォルトを提供する場合、それが有効であると仮定しただけだと思います。ここで行う最も迅速で簡単なことは、引数を解析した直後に単純に検証することです。どうやら、作業を行うために一時ディレクトリを取得しようとしているようです。その場合は、tempfileモジュールを使用して新しいディレクトリを取得します。これを反映するために上記の回答を更新しました。一時ディレクトリを作成し、それをデフォルトの引数として使用し(tempfileは作成するディレクトリが書き込み可能であることを既に保証しています)、プログラムが終了するときに削除するように登録します。

35
mgilson

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

このPathTypeクラスを使用すると、既存のディレクトリと一致するonlyに次の引数タイプを指定できます。メッセージ:

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
25
Dan Lenski

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

_parser.add_argument('launch_directory', type=readable_dir)
_

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

13
jfs