Is vars() the most useless Python built-in ever?

Peter Otten __peter__ at web.de
Tue Dec 1 17:41:20 EST 2015


Manolo Martínez wrote:

> Peter, thanks for taking the time to look into my code.
> 
> On 12/01/15 at 11:40am, Peter Otten wrote:
>> Manolo Martínez wrote:
>> >         def main():  # parse the args and call whatever function was
>> selected
>> >                 try:
>> >                         args = parser.parse_args(sys.argv[1:])
>> >                         args.func(vars(args))
>> >                 except AttributeError as err:
>> >                         if str(err) == "\'Namespace\' object has no
>> attribute \'func\'":
>> >                                 parser.print_help()
>> >                         else:
>> >                                 print("Something has gone wrong:
>> {}".format(err), file = sys.stderr, flush = True)
>> 
>> What probably is typical for a beginner in that snippet is that you don't
>> trust the exception system and handle exceptions that will never occur
>> once the script is debugged. Just write
>> 
>> args = parser.parse_args()
>> args.func(vars(args))
> 
> Well, one fully possible situation is for the user to mistype a
> subcommand. In that case, the script outputs the help provided by
> argparse, so that they know what is and isn't meaningful. That is what
> the "if str(err)..." is doing.
> 
> The else clause is there to output the traceback (in full trust of the
> exception system ;) in case the error was not due to the user mistyping.
> 
> Is there a better way to do this?

As far as I can see in a correctly written script the AttributeError cannot 
be triggered by the user as argparse handles this case automatically by 
showing the help. For example:

$ cat subparsers.py
#!/usr/bin/env python3
import argparse


def foo(args):
    print(args)


def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    foo_parser = subparsers.add_parser("foo")
    foo_parser.set_defaults(func=foo)

    bar_parser = subparsers.add_parser("bar")
    # programming error --> missing func attribute

    args = parser.parse_args()
    args.func(args)


main()
$ ./subparsers.py foo
Namespace(func=<function foo at 0x7ff18e8a2158>)
$ ./subparsers.py bar
Traceback (most recent call last):
  File "./subparsers.py", line 23, in <module>
    main()
  File "./subparsers.py", line 20, in main
    args.func(args)
AttributeError: 'Namespace' object has no attribute 'func'
$ ./subparsers.py baz # erroneous user input
usage: subparsers.py [-h] {foo,bar} ...
subparsers.py: error: invalid choice: 'baz' (choose from 'foo', 'bar')

The traceback may be a bit intimidating, but with your error handling the 
user will get

$ cat subparsers2.py
#!/usr/bin/env python3
import argparse


def foo(args):
    print(args)


def main():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers()

    foo_parser = subparsers.add_parser("foo")
    foo_parser.set_defaults(func=foo)

    bar_parser = subparsers.add_parser("bar")
    # programming error --> missing func attribute


    try:
        args = parser.parse_args()
        args.func(args)
    except AttributeError as err:
        if str(err) == "\'Namespace\' object has no attribute \'func\'":
            print("WE ARE HERE")
            parser.print_help()
        else:
            print("Something has gone wrong: {}".format(err), file = 
sys.stderr, flush = True)

main()
$ ./subparsers2.py foo
Namespace(func=<function foo at 0x7ff9f6fedbf8>)
$ ./subparsers2.py baz # erroneous user input does not trigger your error 
handling
usage: subparsers2.py [-h] {foo,bar} ...
subparsers2.py: error: invalid choice: 'baz' (choose from 'foo', 'bar')
$ ./subparsers2.py bar # but an error in your script does
WE ARE HERE
usage: subparsers2.py [-h] {foo,bar} ...

positional arguments:
  {foo,bar}

optional arguments:
  -h, --help  show this help message and exit

which is very confusing. Again, don't replace the traceback unless you are 
absolutely sure you can do better than the Python interpreter.

By the way, I recommend coverage.py to find dead code. I have not used it 
for too long, and I regret it ;)






More information about the Python-list mailing list