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:
- Call a specific function in response to a specified command line parameter with concise dispatching.
- 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 , . """
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"""
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 .