📜 ⬆️ ⬇️

Two and a half times when working with argparse


The techniques described here are in the official documentation for the argparse module (I use Python 2.7), I did not invent anything new, just using them for a while, I became convinced of their power. They allow you to improve the structure of the program and solve the following tasks:

  1. Call a specific function in response to a specified command line parameter with concise dispatching.
  2. Encapsulation of the processing and validation of user input.


The discussion in the toaster about this issue was the motivation for writing this note:
how to call a specific function in response to a command line parameter
and answers to it in the spirit
I use argparse and if / elif
look towards sys.argv


As an experimental, take a spherical script with two branches of parameters.
userdb.py append <username> <age> userdb.py show <userid> 

The goal, which allows the first reception, will be:
I want for each branch of the arguments to call its own function, which will be responsible for processing all the arguments of the branch, and that this function is automatically selected by the argparse module, without any if / elif, and also ... stop, it is enough for now.
')
Consider the first technique in the example of the first branch of the arguments to append .
 import argparse def create_new_user(args): """      """ #   ,     age = int(args.age) User.add(name=args.username, age=args.age) def parse_args(): """ argparse""" parser = argparse.ArgumentParser(description='User database utility') subparsers = parser.add_subparsers() parser_append = subparsers.add_parser('append', help='Append a new user to database') parser_append.add_argument('username', help='Name of user') parser_append.add_argument('age', help='Age of user') parser_append.set_defaults(func=create_new_user) #     return parser.parse_args() def main(): """ ,        """ args = parse_args() args.func(args) 

Now, if the user launches our script with parameters, for example:
 userdb.py append RootAdminCool 20 

in the depths of the program, the create_new_user () function will be called, which will do everything. Since we have our own function configured for each branch, the main () entry point is spartan short. As you have already noticed, the whole trick lies in calling the set_defaults () method, which allows you to set a fixed parameter with a preset value, in our case there must be a func parameter in all branches with a value — the called object that takes one argument.
By the way, the user, if he has such a desire, will not be able to "outside" slip his parameter as a func, without getting into the script (I did not get it, at least).

Now, nothing remains but to consider the second technique on the second branch of the arguments of our userdb.py.
 userdb.py show <userid> 


The goal for the second trick is as follows: I want the data that the user transmits not only to be validated, it is too simple, but also for my program to operate with more complex objects, formed on the basis of the user's data. In our example, I want the program, instead of userid, to receive an ORM object corresponding to the user with the specified ID.

Notice how, in the first method , in the create_new_user () function, we did “tedious checks” on the validity of the data. Now we will learn to transfer them to where they belong.

In argparse , to help us, there is a parameter that can be set for each argument - type . The type can be any executable object that returns a value that is written to the property of the args object. The simplest examples of using type are
 parser.add_argument(..., type=file) parser.add_argument(..., type=int) 

but we'll go this way a little further:
 import argparse def user_from_db(user_id): """ -,  id  ,   . """ #  user_id id = int(user_id) return User.select(id=id) #   ORM     def print_user(args): """   .    ,  args.userid    ID,   ORM.   ,       (  -  !) """ user_obj = args.userid print str(user_obj) def parse_args(): """ argparse""" parser = argparse.ArgumentParser(description='User database utility') #     subparsers = parser.add_subparsers() parser_show = subparsers.add_parser('show', help='Show information about user') parser_show.add_argument('userid', type=user_from_db, help='ID of user') parser_show.set_defaults(func=print_user) return parser.parse_args() 

Entry point main () does not change!

Now, if we later understand that forcing a user to enter an ID as a parameter is cruel, we can safely switch to, for example, username. To do this, we will only need to change the user_from_db () code, and the print_user () function will not know about anything.

Using the type parameter, you should pay attention to the fact that exceptions that occur inside executable objects that are passed as values ​​of this parameter are processed inside argparse , the user is informed about the error in the corresponding argument.

Half-reception.

This trick did not deserve the title of full-fledged reception, since it is an extension of the second, but this does not diminish its benefits. If you look at the documentation (I’m talking about __doc__) to print_user (), we see that the input is args.userid , in which, in fact, no longer an ID, but a more complex object with complete information about the user. It confuses the code, requires a comment that is ugly. The root of evil is the discrepancy between the information that the user operates on and the information that our program operates on.
It's time to formulate the task:
I want the names of the parameters to be understandable to the user, but at the same time that the code that works with these parameters is expressive.
For this, the positional arguments in argparse have a metavar parameter that specifies the argument name displayed to the user (for optional arguments, the dest parameter is more appropriate).

Now we will try to modify the code from the second example to solve the problem.
 def print_user(args): """   """ print str(args.user_dbobj) def parse_args(): """ argparse""" #     parser = argparse.ArgumentParser(description='User database utility') subparsers = parser.add_subparsers() parser_show = subparsers.add_parser('show', help='Show information about user') parser_show.add_argument('user_dbobj', type=user_from_db, metavar='userid', help='ID of user') parser_show.set_defaults(func=print_user) return parser.parse_args() 

Now the user sees the userid property, and the parameter handler - user_dbobj .
In my opinion, it turned out to solve both 2.5 tasks. As a result, the code that processes data from the user is separated from the main program code, the program entry point is not overloaded with branches, and the user and the program each work in their own terms.

The working code of the example, where all at once “by Feng Shui” is located here .

Source: https://habr.com/ru/post/252333/


All Articles