2012-07-10 39 views
37

我的python腳本需要從在命令行上傳遞的目錄中讀取文件。我已經定義了一個如下所示的readable_dir類型,用於argparse來驗證在命令行上傳遞的目錄是否存在並且可讀。 此外,還爲目錄參數指定了默認值(下例中的/ tmp/non_existent_dir)。 這裏的問題是,即使在命令行中顯式傳入目錄參數的情況下,argparse也會調用默認值的readable_dir()。這會導致腳本出錯,因爲默認路徑/ tmp/non_existent_dir不存在於通過命令行顯式傳入目錄的上下文中。 我可以通過不指定默認值並強制使用此參數來解決此問題,也可以在稍後的腳本中推遲驗證,但這是任何人都知道的更優雅的解決方案?帶argparse的目錄路徑類型

#!/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

有用的代碼示例。提升應該'提高argparse.ArgumentTypeError',否則,我正在挖掘readable_dir類型。 – mlissner 2013-06-30 23:43:15

回答

28

您可以創建自定義操作,而不是一個類型:

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可能更好。

編輯

據我所知,是沒有辦法來驗證默認參數。我想​​開發人員只是假設,如果你提供了一個默認值,那麼它應該是有效的。這裏最快最簡單的事情就是在解析後立即驗證參數。它看起來像,你只是想獲得一個臨時目錄來做一些工作。如果是這樣的話,你可以使用tempfile模塊來獲得一個新的工作目錄。我更新了上面的答案以反映這一點。我創建一個臨時目錄,使用它作爲默認參數(tempfile已經保證它創建的目錄是可寫的),然後我註冊它,當你的程序退出時它將被刪除。

+0

mgilson,我不得不將prospective_dir = values [0]更改爲prospective_dir = values。如果沒有這個,只有來自爭論的第一個字符纔會被拾起。您的解決方案在傳入顯式參數時工作(因爲在這些情況下未驗證默認值)。但是,當沒有參數傳入時,默認值不會被驗證,這是一個問題。 – iruvar 2012-07-10 15:10:15

+0

@cravoori - 我認爲「價值」會成爲一個列表的某些原因。我想只有當'nargs = ...'被指定時纔會發生。無論如何,我不認爲有什麼辦法可以在參數分析完成之後哄騙argparse來進行驗證(這就是你真正要求的)。你必須自己做。我已經更新了我的代碼,以便始終有一個有效的目錄供您在您的程序退出時將其刪除。 (在命令行上指定的目錄不會被刪除)。 – mgilson 2012-07-10 17:16:57

+0

請注意臨時目錄僅用於示例 – iruvar 2012-07-13 02:48:18

11

如果你的腳本不能沒有有效的launch_directory工作,那麼就應該提出的一種強制性的說法:

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

順便說一句,你應該在readable_dir()使用argparse.ArgumentTypeError代替Exception

+2

argparse.ArgumentError(self,「錯誤字符串」)最重要的是,如果您希望用戶看到一個很好的錯誤消息而不是堆棧跟蹤。欲瞭解更多信息,請參閱: http://stackoverflow.com/questions/9881933/catching-argumenttypeerror-exception-from-custom-action – Skotch 2013-03-05 22:20:03

+1

@Skotch:'readable_dir'定義了一個類型,所以'ArgumentTypeError'在這裏適用。我修復了錯字:action - > type – jfs 2013-03-05 22:38:50

+0

J.F. Sebastian:我很確定這是一個我們正在討論的自定義操作(請參閱上面mgilson給出的readable_dir的定義,它是從argparse.Action派生的)。將自定義的argparse操作作爲類型傳遞將不起作用(至少在我嘗試時不會)。 – Apteryx 2015-04-13 18:51:58

13

幾個月前我提交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