2012-07-10 44 views
37

Tập lệnh python của tôi cần đọc các tệp từ thư mục được truyền trên dòng lệnh. Tôi đã định nghĩa một kiểu readable_dir như dưới đây để được sử dụng với argparse để xác nhận rằng thư mục được truyền trên dòng lệnh là tồn tại và có thể đọc được. Ngoài ra, giá trị mặc định (/ tmp/non_existent_dir trong ví dụ bên dưới) cũng đã được chỉ định cho đối số thư mục. Vấn đề ở đây là argparse gọi readable_dir() trên giá trị mặc định ngay cả trong trường hợp đối số thư mục được truyền vào dòng lệnh một cách rõ ràng. Điều này làm cho kịch bản trở nên khó hiểu vì đường dẫn mặc định/tmp/non_existent_dir không tồn tại trong một ngữ cảnh mà thư mục được truyền vào dòng lệnh một cách rõ ràng. Tôi có thể giải quyết vấn đề này bằng cách không chỉ định giá trị mặc định và làm cho đối số này bắt buộc hoặc trì hoãn xác thực cho đến sau này trong tập lệnh nhưng là giải pháp thanh lịch hơn mà mọi người đều biết?loại đường dẫn thư mục với 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

Mẫu mã hữu ích. Nâng cao nên 'nâng argparse.ArgumentTypeError', nhưng nếu không, tôi đang đào loại readable_dir. – mlissner

Trả lời

28

Bạn có thể tạo một hành động tùy chỉnh thay vì một loại:

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) 

Nhưng điều này có vẻ hơi tanh với tôi - nếu không có thư mục được đưa ra, nó đi một thư mục không thể đọc được mà có vẻ để đánh bại mục đích kiểm tra xem thư mục có thể truy cập được ở địa điểm đầu tiên không.

Lưu ý rằng như được nêu trong các nhận xét, nó có thể đẹp hơn đối với
raise argparse.ArgumentError(self, ...) thay vì argparse.ArgumentTypeError.

EDIT

Theo như tôi biết, không có cách nào để xác nhận đối số mặc định. Tôi cho rằng các nhà phát triển argparse chỉ giả định rằng nếu bạn đang cung cấp mặc định, thì nó sẽ hợp lệ. Điều nhanh nhất và dễ nhất để làm ở đây là chỉ cần xác thực các đối số ngay lập tức sau khi bạn phân tích cú pháp chúng. Có vẻ như, bạn chỉ đang cố gắng lấy một thư mục tạm thời để thực hiện một số công việc. Nếu đúng như vậy, bạn có thể sử dụng mô-đun tempfile để có được một thư mục mới để làm việc. Tôi đã cập nhật câu trả lời ở trên để phản ánh điều này. Tôi tạo một thư mục tạm thời, sử dụng nó làm đối số mặc định (tempfile đã đảm bảo thư mục mà nó tạo ra sẽ được ghi) và sau đó tôi đăng ký nó để xóa khi chương trình của bạn thoát.

+0

mgilson, tôi đã phải thay đổi expective_dir = values ​​[0] thành prospective_dir = values.Không có điều này, chỉ có nhân vật đầu tiên từ cuộc tranh cãi được chọn. Giải pháp của bạn hoạt động khi một đối số tường minh được truyền vào (trong đó giá trị mặc định không được xác thực trong các trường hợp này). Tuy nhiên khi không có đối số nào được truyền vào, giá trị mặc định là KHÔNG được xác thực, đó là một vấn đề. – iruvar

+0

@cravoori - Một số lý do tôi nghĩ 'giá trị' sẽ là một danh sách. Tôi cho rằng điều đó chỉ xảy ra khi 'nargs = ...' được chỉ định. Dù sao, tôi không nghĩ rằng có bất kỳ cách nào để dỗ argparse để làm xác nhận sau khi các đối số đã được phân tích cú pháp (đó là những gì bạn đang thực sự yêu cầu). Bạn sẽ phải tự mình làm điều đó. Tôi đã cập nhật mã của mình để luôn có một thư mục hợp lệ để bạn làm việc trong đó sẽ bị xóa khi chương trình của bạn thoát. (các thư mục được chỉ định trên dòng lệnh sẽ không bị xóa). – mgilson

+0

xin lưu ý thư mục tạm thời chỉ được sử dụng một ví dụ – iruvar

11

Nếu kịch bản của bạn không thể làm việc mà không có một giá trị launch_directory sau đó nó phải được thực hiện một cuộc tranh luận bắt buộc:

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

btw, bạn nên sử dụng argparse.ArgumentTypeError thay vì Exception trong readable_dir().

+2

argparse.ArgumentError (tự, "chuỗi lỗi") là tốt nhất của tất cả, nếu bạn muốn người dùng nhìn thấy một thông báo lỗi tốt đẹp thay vì một dấu vết ngăn xếp. Mọi chi tiết, xem: http://stackoverflow.com/questions/9881933/catching-argumenttypeerror-exception-from-custom-action – Skotch

+1

@Skotch: 'readable_dir' định nghĩa một kiểu rất 'ArgumentTypeError' là thích hợp ở đây. Tôi đã khắc phục lỗi đánh máy: hành động -> gõ – jfs

+0

J.F. Sebastian: Tôi khá chắc chắn rằng đó là một hành động tùy chỉnh chúng ta đang nói về (xem định nghĩa của readable_dir do mgilson trên, nó được bắt nguồn từ argparse.Action). Việc chuyển một hành động argparse tùy chỉnh dưới dạng một loại sẽ không hoạt động (ít nhất là nó không xảy ra khi tôi thử). – Apteryx

13

Tôi đã gửi a patch for "path arguments" to the Python standard library mailing list cách đây vài tháng.

Với lớp PathType này, bạn chỉ có thể xác định loại tranh luận sau đây để phù hợp với chỉ một thư mục đang tồn tại - bất cứ điều gì khác sẽ đưa ra một thông báo lỗi:

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

Dưới đây là đoạn code, mà có thể là một cách dễ dàng được sửa đổi để yêu cầu quyền đối với tệp/thư mục cụ thể:

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 
Các vấn đề liên quan