argparse - это модуль для обработки аргументов командной строки.
Примеры того, что позволяет делать модуль:
argparse не единственный модуль для обработки аргументов командной строки.
И даже не единственный такой модуль в стандартной библиотеке.
Мы будем рассматривать только argparse. Но, если Вы столкнетесь с необходимостью использовать подобные модули, обязательно посмотрите и на те модули, которые не входят в стандартную библиотеку Python.
Например, на .
, которая сравнивает разные модули обработки аргументов командной строки (рассматриваются argparse, click и docopt).
Пример скрипта ping_function.py:
import subprocess import argparse def ping_ip(ip_address, count): ''' Ping IP address and return tuple: On success: (return code = 0, command output) On failure: (return code, error output (stderr)) ''' reply = subprocess.run('ping -c {count} -n {ip}' .format(count=count, ip=ip_address), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') if reply.returncode == 0: return True, reply.stdout else: return False, reply.stdout+reply.stderr parser = argparse.ArgumentParser(description='Ping script') parser.add_argument('-a', action="store", dest="ip") parser.add_argument('-c', action="store", dest="count", default=2, type=int) args = parser.parse_args() print(args) rc, message = ping_ip(args.ip, args.count) print(message)
Создание парсера:
parser = argparse.ArgumentParser(description='Ping script')
Добавление аргументов:
parser.add_argument('-a', action="store", dest="ip")
-a
, сохранится в переменную ip
parser.add_argument('-c', action="store", dest="count", default=2, type=int)
-c
, будет сохранен в переменную count
, но, прежде, будет конвертирован в число. Если аргумент не было указан, по умолчанию будет значение 2Строка args = parser.parse_args()
указывается после того, как определены все аргументы.
После её выполнения в переменной args
содержатся все аргументы, которые были переданы скрипту.
К ним можно обращаться, используя синтаксис args.ip
.
Попробуем вызвать скрипт с разными аргументами.
Если переданы оба аргумента:
$ python ping_function.py -a 8.8.8.8 -c 5 Namespace(count=5, ip='8.8.8.8') PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 time=48.673 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=49.902 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=48 time=48.696 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=48 time=50.040 ms 64 bytes from 8.8.8.8: icmp_seq=4 ttl=48 time=48.831 ms --- 8.8.8.8 ping statistics --- 5 packets transmitted, 5 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 48.673/49.228/50.040/0.610 ms
Namespace - это объект, который возвращает метод parse_args()
Передаем только IP-адрес:
$ python ping_function.py -a 8.8.8.8 Namespace(count=2, ip='8.8.8.8') PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 time=48.563 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=49.616 ms --- 8.8.8.8 ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 48.563/49.090/49.616/0.526 ms
Вызов скрипта без аргументов:
$ python ping_function.py Namespace(count=2, ip=None) Traceback (most recent call last): File "ping_function.py", line 31, in <module> rc, message = ping_ip( args.ip, args.count ) File "ping_function.py", line 16, in ping_ip stderr=temp) File "/usr/local/lib/python3.6/subprocess.py", line 336, in check_output **kwargs).stdout File "/usr/local/lib/python3.6/subprocess.py", line 403, in run with Popen(*popenargs, **kwargs) as process: File "/usr/local/lib/python3.6/subprocess.py", line 707, in __init__ restore_signals, start_new_session) File "/usr/local/lib/python3.6/subprocess.py", line 1260, in _execute_child restore_signals, start_new_session, preexec_fn) TypeError: expected str, bytes or os.PathLike object, not NoneType
Если бы функция была вызвана без аргументов, когда не используется argparse, возникла бы ошибка, что не все аргументы указаны.
Но, из-за argparse, фактически аргумент передается, только он равен None
.
Это видно в строке Namespace(count=2, ip=None)
.
В таком скрипте, очевидно, IP-адрес необходимо указывать всегда.
И в argparse можно указать, что аргумент является обязательным.
Надо изменить опцию -a
: добавить в конце required=True
:
parser.add_argument('-a', action="store", dest="ip", required=True)
Теперь, если вызвать скрипт без аргументов, вывод будет таким:
$ python ping_function.py usage: ping_function.py [-h] -a IP [-c COUNT] ping_function.py: error: the following arguments are required: -a
Теперь отображается понятное сообщение, что надо указать обязательный аргумент, и подсказка usage.
Также, благодаря argparse, доступен help:
$ python ping_function.py -h usage: ping_function.py [-h] -a IP [-c COUNT] Ping script optional arguments: -h, --help show this help message and exit -a IP -c COUNT
Обратите внимание, что в сообщении все опции находятся в секции optional arguments
.
argparse сам определяет, что указаны опции, так как они начинаются с -
и в имени только одна буква.
Зададим IP-адрес как позиционный аргумент.
Файл ping_function_ver2.py:
import subprocess from tempfile import TemporaryFile import argparse def ping_ip(ip_address, count): ''' Ping IP address and return tuple: On success: (return code = 0, command output) On failure: (return code, error output (stderr)) ''' reply = subprocess.run('ping -c {count} -n {ip}' .format(count=count, ip=ip_address), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') if reply.returncode == 0: return True, reply.stdout else: return False, reply.stdout+reply.stderr parser = argparse.ArgumentParser(description='Ping script') parser.add_argument('host', action="store", help="IP or name to ping") parser.add_argument('-c', action="store", dest="count", default=2, type=int, help="Number of packets") args = parser.parse_args() print(args) rc, message = ping_ip( args.host, args.count ) print(message)
Теперь, вместо указания опции -a
, можно просто передать IP-адрес.
Он будет автоматически сохранен в переменной host
.
И автоматически считается обязательным.
То есть, теперь не нужно указывать required=True
и dest="ip"
.
Кроме того, в скрипте указаны сообщения, которые будут выводиться при вызове help.
Теперь вызов скрипта выглядит так:
$ python ping_function_ver2.py 8.8.8.8 -c 2 Namespace(host='8.8.8.8', count=2) PING 8.8.8.8 (8.8.8.8): 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=48 time=49.203 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=51.764 ms --- 8.8.8.8 ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 49.203/50.484/51.764/1.280 ms
А сообщение help так:
$ python ping_function_ver2.py -h usage: ping_function_ver2.py [-h] [-c COUNT] host Ping script positional arguments: host IP or name to ping optional arguments: -h, --help show this help message and exit -c COUNT Number of packets
Рассмотрим один из способов организации более сложной иерархии аргументов.
Этот пример покажет больше возможностей argparse, но они этим не ограничиваются, поэтому, если Вы будете использовать argparse, обязательно посмотрите или .
Файл parse_dhcp_snooping.py:
# -*- coding: utf-8 -*- import argparse # Default values: DFLT_DB_NAME = 'dhcp_snooping.db' DFLT_DB_SCHEMA = 'dhcp_snooping_schema.sql' def create(args): print("Creating DB {} with DB schema {}".format((args.name, args.schema))) def add(args): if args.sw_true: print("Adding switch data to database") else: print("Reading info from file(s) \n{}".format(', '.join(args.filename))) print("\nAdding data to db {}".format(args.db_file)) def get(args): if args.key and args.value: print("Geting data from DB: {}".format(args.db_file)) print("Request data for host(s) with {} {}".format((args.key, args.value))) elif args.key or args.value: print("Please give two or zero args\n") print(show_subparser_help('get')) else: print("Showing {} content...".format(args.db_file)) parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='description') create_parser = subparsers.add_parser('create_db', help='create new db') create_parser.add_argument('-n', metavar='db-filename', dest='name', default=DFLT_DB_NAME, help='db filename') create_parser.add_argument('-s', dest='schema', default=DFLT_DB_SCHEMA, help='db schema filename') create_parser.set_defaults( func=create ) add_parser = subparsers.add_parser('add', help='add data to db') add_parser.add_argument('filename', nargs='+', help='file(s) to add to db') add_parser.add_argument('--db', dest='db_file', default=DFLT_DB_NAME, help='db name') add_parser.add_argument('-s', dest='sw_true', action='store_true', help='add switch data if set, else add normal data') add_parser.set_defaults( func=add ) get_parser = subparsers.add_parser('get', help='get data from db') get_parser.add_argument('--db', dest='db_file', default=DFLT_DB_NAME, help='db name') get_parser.add_argument('-k', dest="key", choices=['mac', 'ip', 'vlan', 'interface', 'switch'], help='host key (parameter) to search') get_parser.add_argument('-v', dest="value", help='value of key') get_parser.add_argument('-a', action='store_true', help='show db content') get_parser.set_defaults( func=get ) if __name__ == '__main__': args = parser.parse_args() if not vars(args): parser.print_usage() else: args.func(args)
Теперь создается не только парсер, как в прошлом примере, но и вложенные парсеры.
Вложенные парсеры будут отображаться как команды.
Но, фактически, они будут использоваться как обязательные аргументы.
С помощью вложенных парсеров создается иерархия аргументов и опций.
Аргументы, которые добавлены во вложенный парсер, будут доступны как аргументы этого парсера.
Например, в этой части создан вложенный парсер create_db, и к нему добавлена опция -n
:
create_parser = subparsers.add_parser('create_db', help='create new db') create_parser.add_argument('-n', dest='name', default=DFLT_DB_NAME, help='db filename')
Синтаксис создания вложенных парсеров и добавления к ним аргументов одинаков:
create_parser = subparsers.add_parser('create_db', help='create new db') create_parser.add_argument('-n', metavar='db-filename', dest='name', default=DFLT_DB_NAME, help='db filename') create_parser.add_argument('-s', dest='schema', default=DFLT_DB_SCHEMA, help='db schema filename') create_parser.set_defaults( func=create )
Метод add_argument
добавляет аргумент.
Тут синтаксис точно такой же, как и без использования вложенных парсеров.
В строке create_parser.set_defaults( func=create ) указывается, что при вызове парсера create_parser будет вызвана функция create.
Функция create получает как аргумент все аргументы, которые были переданы.
И внутри функции можно обращаться к нужным:
def create(args): print("Creating DB {} with DB schema {}".format((args.name, args.schema)))
Если вызвать help для этого скрипта, вывод будет таким:
$ python parse_dhcp_snooping.py -h usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ... optional arguments: -h, --help show this help message and exit subcommands: valid subcommands {create_db,add,get} description create_db create new db add add data to db get get data from db
Обратите внимание, что каждый вложенный парсер, который создан в скрипте, отображается как команда в подсказке usage:
usage: parse_dhcp_snooping.py [-h] {create_db,add,get} ...
У каждого вложенного парсера теперь есть свой help:
$ python parse_dhcp_snooping.py create_db -h usage: parse_dhcp_snooping.py create_db [-h] [-n db-filename] [-s SCHEMA] optional arguments: -h, --help show this help message and exit -n db-filename db filename -s SCHEMA db schema filename
Кроме вложенных парсеров, в этом примере также есть несколько новых возможностей argparse.
metavar
В парсере create_parser используется новый аргумент - metavar
:
create_parser.add_argument('-n', metavar='db-filename', dest='name', default=DFLT_DB_NAME, help='db filename') create_parser.add_argument('-s', dest='schema', default=DFLT_DB_SCHEMA, help='db schema filename')
Аргумент metavar
позволяет указывать имя аргумента для вывода в сообщении usage и help:
$ python parse_dhcp_snooping.py create_db -h usage: parse_dhcp_snooping.py create_db [-h] [-n db-filename] [-s SCHEMA] optional arguments: -h, --help show this help message and exit -n db-filename db filename -s SCHEMA db schema filename
Посмотрите на разницу между опциями -n
и -s
:
-n
и в usage, и в help указывается имя, которое указано в параметре metavar-s
указывается имя переменной, в которую сохраняется значениеnargs
В парсере add_parser используется nargs
:
add_parser.add_argument('filename', nargs='+', help='file(s) to add to db')
nargs
позволяет указать, что в этот аргумент должно попасть определенное количество элементов.
В этом случае все аргументы, которые были переданы скрипту после имени аргумента filename
,
попадут в список nargs.
Но должен быть передан хотя бы один аргумент.
Сообщение help в таком случае выглядит так:
$ python parse_dhcp_snooping.py add -h usage: parse_dhcp_snooping.py add [-h] [--db DB_FILE] [-s] filename [filename ...] positional arguments: filename file(s) to add to db optional arguments: -h, --help show this help message and exit --db DB_FILE db name -s add switch data if set, else add normal data
Если передать несколько файлов, они попадут в список.
А так как функция add просто выводит имена файлов, вывод получится таким:
$ python parse_dhcp_snooping.py add filename test1.txt test2.txt Reading info from file(s) filename, test1.txt, test2.txt Adding data to db dhcp_snooping.db
nargs
поддерживает такие значения:
N
- должно быть указанное количество аргументов. Аргументы будут в списке (даже если указан 1)?
- 0 или 1 аргумент*
- все аргументы попадут в список +
- все аргументы попадут в список, но должен быть передан хотя бы один аргументchoices
В парсере get_parser используется choices
:
get_parser.add_argument('-k', dest="key", choices=['mac', 'ip', 'vlan', 'interface', 'switch'], help='host key (parameter) to search')
Для некоторых аргументов важно, чтобы значение было выбрано только из определенных вариантов.
Для таких случаев можно указывать choices
.
Для этого парсера help выглядит так:
$ python parse_dhcp_snooping.py get -h usage: parse_dhcp_snooping.py get [-h] [--db DB_FILE] [-k {mac,ip,vlan,interface,switch}] [-v VALUE] [-a] optional arguments: -h, --help show this help message and exit --db DB_FILE db name -k {mac,ip,vlan,interface,switch} host key (parameter) to search -v VALUE value of key -a show db content
А если выбрать неправильный вариант:
$ python parse_dhcp_snooping.py get -k test usage: parse_dhcp_snooping.py get [-h] [--db DB_FILE] [-k {mac,ip,vlan,interface,switch}] [-v VALUE] [-a] parse_dhcp_snooping.py get: error: argument -k: invalid choice: 'test' (choose from 'mac', 'ip', 'vlan', 'interface', 'switch')
В данном примере важно указать варианты на выбор, так как затем на основании выбранного варианта генерируется SQL-запрос. И, благодаря
choices
, нет возможности указать какой-то параметр, кроме разрешенных.
В файле parse_dhcp_snooping.py последние две строки будут выполняться только в том случае, если скрипт был вызван как основной.
if __name__ == '__main__': args = parser.parse_args() args.func(args)
А значит, если импортировать файл, эти строки не будут вызваны.
Попробуем импортировать парсер в другой файл (файл call_pds.py):
from parse_dhcp_snooping import parser args = parser.parse_args() args.func(args)
Вызов сообщения help:
$ python call_pds.py -h usage: call_pds.py [-h] {create_db,add,get} ... optional arguments: -h, --help show this help message and exit subcommands: valid subcommands {create_db,add,get} description create_db create new db add add data to db get get data from db
Вызов аргумента:
$ python call_pds.py add test.txt test2.txt Reading info from file(s) test.txt, test2.txt Adding data to db dhcp_snooping.db
Всё работает без проблем.
Последняя особенность argparse - возможность передавать аргументы вручную.
Аргументы можно передать как список при вызове метода parse_args()
(файл call_pds2.py):
from parse_dhcp_snooping import parser, get args = parser.parse_args('add test.txt test2.txt'.split()) args.func(args)
Необходимо использовать метод split(), так как метод
parse_args
ожидает список аргументов.
Результат будет таким, как если бы скрипт был вызван с аргументами:
$ python call_pds2.py Reading info from file(s) test.txt, test2.txt Adding data to db dhcp_snooping.db