python / cpython

The Python programming language
https://www.python.org
Other
63.46k stars 30.39k forks source link

argparse: capturing actions #87212

Open 167ea586-216e-4ac4-a3cf-b7b37ad7a4b1 opened 3 years ago

167ea586-216e-4ac4-a3cf-b7b37ad7a4b1 commented 3 years ago
BPO 43046
Nosy @rhettinger, @bitdancer, @monkeyman79
PRs
  • python/cpython#24357
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields: ```python assignee = None closed_at = None created_at = labels = ['type-feature', 'library', '3.10'] title = 'argparse: capturing actions' updated_at = user = 'https://github.com/monkeyman79' ``` bugs.python.org fields: ```python activity = actor = 'v+python' assignee = 'none' closed = False closed_date = None closer = None components = ['Library (Lib)'] creation = creator = 'monkeyman79' dependencies = [] files = [] hgrepos = [] issue_num = 43046 keywords = ['patch'] message_count = 8.0 messages = ['385824', '385826', '385832', '385835', '385836', '385837', '385843', '385882'] nosy_count = 5.0 nosy_names = ['rhettinger', 'v+python', 'r.david.murray', 'paul.j3', 'monkeyman79'] pr_nums = ['24357'] priority = 'normal' resolution = None stage = 'patch review' status = 'open' superseder = None type = 'enhancement' url = 'https://bugs.python.org/issue43046' versions = ['Python 3.10'] ```

    167ea586-216e-4ac4-a3cf-b7b37ad7a4b1 commented 3 years ago

    This is spinoff from bpo-42973.

    The purpose of this issue is to provide set of additional action classes for argparse, that could capture mutual relationship between command line arguments for more advanced parsers, where order of command line arguments is meaningful. It is hoped that this will work together with enhancement introduced in bpo-42973, providing also ability to capture relationship between options and positional parameters.

    There will be four new action classes: 'extend_capture', 'append_capture', 'store_capture' and 'capture'.

    Here are excerpts from documentation being prepared for the new action classes and an example of use:

        >>> parser = argparse.ArgumentParser()
        >>> parser.add_argument("--load-addr", type=lambda s: int(s, 16))
        >>> parser.add_argument("--exec-addr", type=lambda s: int(s, 16))
        >>> parser.add_argument("--replace", action="store_true")
        >>> parser.add_argument("--file", nargs="*", action="extend_capture",
        ...     capture="*", capture_reset=["load_addr", "exec_addr"])
        >>> parser.parse_args("--replace --load-addr 1900 --exec-addr 8023 "
        ...     "--file CALC !BOOT".split())
        Namespace(load_addr=None, exec_addr=None, replace=True, file=[
            {'replace': True, 'exec_addr': 32803, 'load_addr': 6400, 'file': 'CALC'},
            {'replace': True, 'exec_addr': None, 'load_addr': None, 'file': '!BOOT'}])

    capture

    The list of attributes to capture. This can be either a single attribute name, a list (or other iterable type) of names or special value '*'. Name of attribute associated with each argument is determined by the dest keyword passed to addargument method when the argument was created. If capture is '*', all attributes are captured, except for this argument's own value.

    This keyword argument is valid only for 'extend_capture', 'append_capture', 'store_capture' and 'capture' actions.

    capture_reset

    The list of attributes to capture and reset to default value. As with capture, this can be '*' to capture and reset all attributes except for this argument's own value.

    This keyword argument is valid only for 'extend_capture', 'append_capture', 'store_capture' and 'capture' actions.

    key

    The key to use for adding this argument's own command-line value to dictionary of captured values. If this keyword argument is not specified, the dest is used.

    This keyword argument is valid only for 'extend_capture', 'append_capture' and 'store_capture' actions.

    b5a9ce10-d67f-478f-ab78-b08d099eb753 commented 3 years ago

    I'm a little confused by the mention of the "key" keyword argument. I suspect that is an internal concept to argparse, possibly passed that way to internal methods, but on the add_argument interface, it doesn't exist... instead there is "name or flags" positional arguments, from which, together with the "dest" argument, the "key" keyword argument is derived. This is described under the explanation for the "dest" parameter.

    But in the user documentation, the use of "key" is a surprise (to me, maybe I missed something).

    Perhaps using "dest" rather than "key" in this documentation would be better? Or perhaps there is a precedent in the argparse documentation for what to call it?

    167ea586-216e-4ac4-a3cf-b7b37ad7a4b1 commented 3 years ago

    I'm a little confused by the mention of the "key" keyword argument. I suspect that is an internal concept to argparse, possibly passed that way to internal methods, but on the add_argument interface, it doesn't exist... instead there is "name or flags" positional arguments, from which, together with the "dest" argument, the "key" keyword argument is derived. This is described under the explanation for the "dest" parameter.

    Hmm... that may be confusing. The "key" keyword argument is not internal concept, is new parameter that can be passed to add_argument, specifically for 'capture' actions. The "key" name may be unfortunate, maybe it could be "dest_key" or "dict_key"? It can't be "dest", because it is goes to add_argument together with "dest". In most cases it is not needed and "dest" is used as dictionary key, but user may want to override that. Like in this example:

        >>> parser = argparse.ArgumentParser()
        >>> parser.add_argument("--user", default=None)
        >>> parser.add_argument("--server", default="localhost")
        >>> parser.add_argument("src1", action="store_capture", key="file", capture_reset=["user", "server"])
        >>> parser.add_argument("src2", action="store_capture", key="file", capture_reset=["user", "server"])
        >>> parser.add_argument("dst", action="store_capture", key="file", capture_reset=["user", "server"])
        >>> parser.parse_args("first --user guest --server no_such second --server not_found third".split())
        Namespace(user=None, server='localhost',
            src1={'user': None, 'server': 'localhost', 'file': 'first'},
            src2={'user': 'guest', 'server': 'no_such', 'file': 'second'},
            dst={'user': None, 'server': 'not_found', 'file': 'third'})

    The 'dest' is 'src1', 'src2' and 'dst' respectively, but we want to have unified layout of the dictionaries, so 'key' is 'file' in all three cases.

    Oh, and I forgot to update add_argument signature in rst earlier.

    ArgumentParser.add_argument(name or flags..., [action], [nargs], \
                               [const], [default], [type], [choices], [required], \
                               [help], [metavar], [dest], [capture], \
                               [capture_reset], [key])
    ...
    ...
       * dest - The name of the attribute to be added to the object returned by
         parse_args.

    -> these are new:

    rhettinger commented 3 years ago

    capture mutual relationship between command line arguments for more advanced parsers, where order of command line arguments is meaningful

    I don't think this is a pattern we should encourage. It doesn't seem to arise often and it makes the API much more complex for end users.

    Also, I'm concerned that adding more action classes will make argparse harder to learn for the average user. Already, the module suffers from sprawl and has unexpected interactions between the components. The docs and tutorial are voluminous and hard to digest in a single sitting.

    ISTM the scope of argparse was never intended to capture all possible patterns for command line argument parsing. Instead, it aimed at to address the common cases.

    b5a9ce10-d67f-478f-ab78-b08d099eb753 commented 3 years ago

    So the missing signature is why I didn't understand, probably. At least, it seems reasonable to blame that :) You didn't include [version] in the signature, but that isn't your fault: it isn't in the original and should be (see action "version").

    So key is optional and defaults to dest... I saw that, but was already confused. This latest example clears up why you might want to overrride dest for use in different arguments... very similar to why dest is allowed to be specified instead of its default value.

    And dest could be used, it is sufficient, but allowing specification of key to override it is more flexible, and could save user code in some circumstances, with little cost to the implementation.

    Sounds good to me now.

    I was also surprised by introduction of a "capture" action by itself, and look forward to documentation for it, as things progress. I can guess about store_capture and append_capture from the definition of extend_capture.

    b5a9ce10-d67f-478f-ab78-b08d099eb753 commented 3 years ago

    Raymond said: ISTM the scope of argparse was never intended to capture all possible patterns for command line argument parsing. Instead, it aimed at to address the common cases.

    I say: Sounds like another wet blanket argpment. Refer to the section "Why aren't getopt and optparse enough?" in the argparse PEP-389. It is clearn that argparse was intended to implement more complex cases than could be easily implemented with the prior getopt and optparse. The concepts of variable numbers of arguments for an option, and of subcommonds, introduced initially in argparse, are far more complex than the enhancements proposed here.

    rhettinger commented 3 years ago

    Sounds like another wet blanket argpment

    Please watch your tone. It borders on being abusive.

    It is entirely appropriate to set limits on the scope of what a module is trying to achieve. It is appropriate to consider module learnability. It is appropriate to limit sprawl and feature creep. It is appropriate to consider whether the pattern being encouraged and supported by the PR is intellectually manageable by end users.

    It is correct that argparse set out to handle more complex cases than optparse. But even then, it was aiming to cases that commonly arose it the wild (subparsers are not uncommon). It never aspired to be all things to all users.

    b5a9ce10-d67f-478f-ab78-b08d099eb753 commented 3 years ago

    Raymond Hettinger \raymond.hettinger@gmail.com\ added the comment:

    > Sounds like another wet blanket argpment

    Please watch your tone. It borders on being abusive.

    I considered that as a canonical description of the type of negativity presented by your comment. It was not intended to be abusive, just a descriptive summary.

    It is entirely appropriate to set limits on the scope of what a module is trying to achieve. It is appropriate to consider module learnability. It is appropriate to limit sprawl and feature creep. It is appropriate to consider whether the pattern being encouraged and supported by the PR is intellectually manageable by end users.

    I agree that all those considerations are worthwhile, but I see this as a small extension that adds significant power. No one is forced to use the new features, or the old features, or argparse at all. There are already features in argparse that I simply ignore, because I have no need for them. It doesn't make using the feature I do use more difficult. I just look for what I need, and use those features.

    It is correct that argparse set out to handle more complex cases than optparse. But even then, it was aiming to cases that commonly arose it the wild (subparsers are not uncommon). It never aspired to be all things to all users.

    And unfortunately, in so doing, it failed to support some cases that optparse could handle, although for the cases that argparse could handle, the user code to use it was far simpler. This was improved somewhat by the addition of parse_intermixed_args.

    Since argparse is provided in stdlib, it seems very reasonable to me to enhance it to support command line parsing paradigms used effectively since 1979, at least (ref: tar).