2010-04-28 29 views
7

My Python script (đối với danh sách todo) được bắt đầu từ dòng lệnh như thế này:Làm cách nào để bạn xử lý các tùy chọn không thể sử dụng cùng nhau (sử dụng OptionParser)?

todo [options] <command> [command-options] 

Một số tùy chọn không thể được sử dụng cùng nhau, ví dụ

todo add --pos=3 --end "Ask Stackoverflow" 

sẽ xác định được vị trí thứ ba và cuối danh sách. Tương tự như vậy

todo list --brief --informative 

sẽ gây nhầm lẫn cho chương trình của tôi về tóm tắt hoặc cung cấp thông tin. Vì tôi muốn có một sự kiểm soát khá mạnh mẽ, những trường hợp như thế này sẽ là một bó, và những cái mới chắc chắn sẽ nảy sinh trong tương lai. Nếu người dùng vượt qua kết hợp các tùy chọn không hợp lệ, tôi muốn đưa ra một thông báo mang tính thông tin, tốt nhất là cùng với trợ giúp sử dụng do optparse cung cấp. Hiện tại tôi xử lý điều này với một tuyên bố if-else mà tôi thấy thực sự xấu xí và nghèo nàn. Ước mơ của tôi là có một cái gì đó như thế này trong mã của tôi:

parser.set_not_allowed(combination=["--pos", "--end"], 
         message="--pos and --end can not be used together") 

và OptionParser sẽ sử dụng điều này khi phân tích cú pháp các tùy chọn.

Vì điều này không tồn tại như tôi biết, tôi hỏi cộng đồng SO: Làm cách nào để bạn xử lý việc này?

Trả lời

6

Có thể bằng cách mở rộng optparse.OptionParser:

class Conflict(object): 
    __slots__ = ("combination", "message", "parser") 

    def __init__(self, combination, message, parser): 
     self.combination = combination 
     self.message = str(message) 
     self.parser = parser 

    def accepts(self, options): 
     count = sum(1 for option in self.combination if hasattr(options, option)) 
     return count <= 1 

class ConflictError(Exception): 
    def __init__(self, conflict): 
     self.conflict = conflict 

    def __str__(self): 
     return self.conflict.message 

class MyOptionParser(optparse.OptionParser): 
    def __init__(self, *args, **kwds): 
     optparse.OptionParser.__init__(self, *args, **kwds) 
     self.conflicts = [] 

    def set_not_allowed(self, combination, message): 
     self.conflicts.append(Conflict(combination, message, self)) 

    def parse_args(self, *args, **kwds): 
     # Force-ignore the default values and parse the arguments first 
     kwds2 = dict(kwds) 
     kwds2["values"] = optparse.Values() 
     options, _ = optparse.OptionParser.parse_args(self, *args, **kwds2) 

     # Check for conflicts 
     for conflict in self.conflicts: 
      if not conflict.accepts(options): 
       raise ConflictError(conflict) 

     # Parse the arguments once again, now with defaults 
     return optparse.OptionParser.parse_args(self, *args, **kwds) 

Sau đó bạn có thể xử lý ConflictError nơi bạn gọi parse_args: Câu trả lời

try: 
    options, args = parser.parse_args() 
except ConflictError as err: 
    parser.error(err.message) 
+0

Giải pháp tuyệt vời! – Joel

+0

'super()' sẽ không hoạt động đối với Python 2.X, vì 'OptionParser' được tạo ra như một lớp kiểu cũ. Một giải pháp được cung cấp trên [Stack Overflow question 2023940] (http://stackoverflow.com/questions/2023940/using-super-when-subclassing-python-class-that-is-not-derived-from-objectold). – gotgenes

+0

Cảm ơn, tôi đã sửa nó bằng cách đánh vần siêu lớp một cách rõ ràng. –

3

của Tamás là một khởi đầu tốt, nhưng tôi không thể có được nó để làm việc, vì nó có (hoặc có) một số lỗi, bao gồm một cuộc gọi bị hỏng đến siêu , "parser" bị thiếu trong Conflict.__slots__, luôn gây ra lỗi khi xung đột là quy định vì việc sử dụng parser.has_option() trong Conflicts.accepts() vv

Kể từ khi tôi thực sự cần tính năng này, tôi cuộn giải pháp của riêng tôi và đã làm cho nó có sẵn từ các Python Package Index như ConflictsOptionParser. Nó hoạt động khá nhiều như là một thay thế thả cho optparse.OptionParser. (Tôi biết argparse là dòng lệnh mới phân tích cú pháp nóng, nhưng nó không có sẵn trong Python 2.6 trở xuống và có ít sự chấp nhận hơn optparse. Gửi email cho tôi nếu bạn muốn hack hoặc đã hack một số argparse bổ sung giải pháp dựa trên) Điều quan trọng là hai phương pháp mới, register_conflict(), và, đến một mức độ thấp hơn, unregister_conflict():.

#/usr/bin/env python 

import conflictsparse 
parser = conflictsparse.ConflictsOptionParser("python %prog [OPTIONS] ARG") 
# You can retain the Option instances for flexibility, in case you change 
# option strings later 
verbose_opt = parser.add_option('-v', '--verbose', action='store_true') 
quiet_opt = parser.add_option('-q', '--quiet', action='store_true') 
# Alternatively, you don't need to keep references to the instances; 
# we can re-use the option strings later 
parser.add_option('--no-output', action='store_true') 
# Register the conflict. Specifying an error message is optional; the 
# generic one that is generated will usually do. 
parser.register_conflict((verbose_opt, quiet_opt, '--no-output')) 
# Now we parse the arguments as we would with 
# optparse.OptionParser.parse_args() 
opts, args = parser.parse_args() 

Nó có một vài ưu điểm so với các giải pháp bắt đầu bởi Tamas:

  • Nó hoạt động ra khỏi hộp và có thể cài đặt qua pip (hoặc easy_install, nếu bạn phải).
  • Các tùy chọn trong xung đột có thể được chỉ định bằng chuỗi tùy chọn của chúng hoặc theo các trường hợp optparse.Option, giúp với nguyên tắc DRY; nếu bạn sử dụng các cá thể, bạn có thể thay đổi các chuỗi thực tế mà không phải lo lắng về việc phá vỡ mã xung đột.
  • Nó tuân theo hành vi optparse.OptionParser.parse_args() bình thường và tự động gọi optparse.OptionParser.error() khi phát hiện các tùy chọn xung đột trong đối số dòng lệnh, thay vì ném trực tiếp lỗi. (Đây là một tính năng và một lỗi, loại lỗi trong thiết kế chung của optparse, nhưng một tính năng cho gói này ở chỗ nó ít nhất là phù hợp với hành vi optparse.)
Các vấn đề liên quan