如何处理不能一起使用的选项(使用 OptionParser)?

发布于 2024-08-30 18:37:42 字数 708 浏览 5 评论 0原文

我的 Python 脚本(用于待办事项列表)是从命令行启动的,如下所示:

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

有些选项不能一起使用,例如

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

指定列表的第三个位置和末尾。同样,

todo list --brief --informative

我的程序也会因简短或信息丰富而感到困惑。由于我想要一个相当强大的选项控制,所以这样的情况会很多,而且以后肯定还会出现新的情况。如果用户传递了错误的选项组合,我想提供一条信息性消息,最好连同 optparse 提供的使用帮助一起。目前我用 if-else 语句来处理这个问题,我发现它真的很难看而且很糟糕。我的梦想是在我的代码中拥有这样的东西:

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

并且 OptionParser 在解析选项时会使用它。

据我所知,这并不存在,我向 SO 社区询问: 你如何处理这个问题?

My Python script (for todo lists) is started from the command line like this:

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

Some options can not be used together, for example

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

would specify both the third position and the end of the list. Likewise

todo list --brief --informative

would confuse my program about being brief or informative. Since I want to have quite a powerful option control, cases like these will be a bunch, and new ones will surely arise in the future. If a users passes a bad combination of options, I want to give an informative message, preferably along with the usage help provided by optparse. Currently I handle this with an if-else statement that I find really ugly and poor. My dream is to have something like this in my code:

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

and the OptionParser would use this when parsing the options.

Since this doesn't exist as far as I know, I ask the SO community:
How do you handle this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

万劫不复 2024-09-06 18:37:42

可能通过扩展 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)

然后您可以在调用 parse_args 的地方处理 ConflictError :

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

Possibly by extending 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)

You can then handle ConflictError where you call parse_args:

try:
    options, args = parser.parse_args()
except ConflictError as err:
    parser.error(err.message)
遇到 2024-09-06 18:37:42

Tamás 的答案是一个好的开始,但我无法让它工作,因为它有(或有)许多错误,包括对 super 的调用失败“解析器”< Conflict.__slots__ 中缺少 /code>,由于在 Conflicts.accepts() 中使用了 parser.has_option(),因此在指定冲突时始终会引发错误) 等。

由于我确实需要此功能,因此我推出了自己的解决方案,并从 Python 中获取它包索引ConflictsOptionParser。它几乎可以替代 optparse.OptionParser。 (我确实知道 argparse 是新的命令行解析热点,但它在 Python 2.6 及更低版本中不可用,并且目前的采用率低于 optparse,如果您想破解或已经破解了额外的 argparse,请给我发送电子邮件。基于 code> 的解决方案。)关键是两个新方法,register_conflict(),以及较小程度上的 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()

与由 Támas 开始的解决方案:

  • 它开箱即用,可以通过 pip (或 easy_install,如果必须的话)。
  • 冲突中的选项可以通过它们的选项字符串或它们的 optparse.Option 实例来指定,这有助于 DRY 原则;如果您使用实例,您可以更改实际的字符串,而不必担心破坏冲突代码。
  • 它遵循正常的 optparse.OptionParser.parse_args() 行为,并在检测到命令行参数中存在冲突选项时自动调用 optparse.OptionParser.error(),而不是抛出直接报错。 (这既是一个功能,也是一个错误;在 optparse 的总体设计中是一个错误,但是这个包的一个功能,因为它至少与 optparse 一致行为。)

Tamás's answer is a good start, but I couldn't get it to work, as it has (or had) a number of bugs, including a broken call to super, "parser" missing in Conflict.__slots__, always raising an error when a conflict is specified because of the use of parser.has_option() in Conflicts.accepts(), etc.

Since I really needed this feature, I rolled my own solution and have made it available from the Python Package Index as ConflictsOptionParser. It works pretty much as a drop in replacement for optparse.OptionParser. (I do know argparse is the new command line parsing hotness, but it is not available in Python 2.6 and below and has less adoption currently than optparse. Send me an email if you'd like to hack up or have hacked up an additional argparse-based solution.) The key is two new methods, register_conflict(), and, to a lesser extent, 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()

It has a few advantages over the solution begun by Támas:

  • It works out of the box and is installable through pip (or easy_install, if you must).
  • Options in a conflict may be specified either by their option strings or by their optparse.Option instances, which helps with the DRY principle; if you use the instances, you can change the actual strings without worrying about breaking conflict code.
  • It follows normal optparse.OptionParser.parse_args() behavior and automatically calls optparse.OptionParser.error() when it detects conflicting options in the command line arguments, rather than throwing the error directly. (This is both a feature and a bug; kind of a bug in optparse's general design, but a feature for this package in that it is at least consistent with optparse behavior.)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文