argparse Python

Введение

Это статья про библиотеку argparse для работы с агрументами, которые передаются в скрипт извне. Например, из терминала.

Передача аргументов в скрипт это распространённая практика для CI/CD.

Про обычные аргументы, то есть такие, которые передаются в функцию внутри модуля, читайте статью *args, **kwargs

Про применение sys.argv[] без обёрток читайте здесь

Здесь будут использованы приёмы, которые можно изучить в следующих статьях:

Exceptions , Менеджер контекста , Списки , Строки , Работа с файлами

Пример

import argparse parser = argparse.ArgumentParser() parser.add_argument("filename", help="file to read") args = parser.parse_args() print(args)

python argparse_demo.py site.txt

Namespace(filename='site.txt')

filename попал в Namespace, так как по умолчанию над всеми аргументами совершается действие (action="store")

Можно было явно указать это слеюущим образом:

import argparse parser = argparse.ArgumentParser() parser.add_argument("filename", action="store", help="file to read") …

Если не передать ни одного аргумента

python argparse_demo.py

usage: argparse_demo.py [-h] filename argparse_demo.py: error: the following arguments are required: filename

Если передать лишний аргумент

python argparse_demo.py site.txt demo.txt

usage: argparse_demo.py [-h] filename argparse_demo.py: error: unrecognized arguments: demo.txt

Получить справку по аргументу можно с помощью опции -h

python argparse_demo.py -h filename

usage: argparse_demo.py [-h] filename positional arguments: filename file to read options: -h, --help show this help message and exit

version

У метода add_argument() есть несколько ключевых аргументов.

version это один из них.

Обычно это действие action="version" используется вместе с флагом --version

Скрипт, вызванный с флагом, у которого стоит action="version" не будет выполняться по стандартному сценарию.

Вместо этого будет выведено значение aргумента version, которые можно сформировать определённым образом.

Слово version здесь очень перегружено. Чтобы не запутаться ещё раз отметим:

--version - это флаг, который используется при вызове скрипта.

action="version" - указывает, что действие, которое выполняет этот флаг это уже не store а version

version="%(prog)s 2.0" - указывает на то как именно выводить версию.

# argparse_version.py import argparse parser = argparse.ArgumentParser(description="version action demo", prog="PROG") parser.add_argument("--version", "-v", action="version", version="%(prog)s 2.0") args = parser.parse_args() print(args)

Если не задать prog= будет использовано имя файла.

python argparse_version.py --version

PROG 2.0

Видим, что после вывода версии не был напечатан Namespace(), так как скрипт не отработал дефолтный сценарий.

В следующей главе разберём необязательные аргументы с дефолтным action="store"

Необязательный аргумент

Чтобы показать, что аргумент является необязательным перед его именем добавляют два дефиса --.

Такой аргумент часто называют флагом. Обычно после длинной версии с двумя дефисами, через запятую добавляют короткую версию из одной буквы с одним дефисом -.

Это стандартная практика, которая не ограничивается скриптами на Python, а используется почти везде.

Типичный пример такого аргумента во многих скриптах это --version, -v

Добавим два необязательных аргумента: --limit и --version

# reverse_file.py import argparse parser = argparse.ArgumentParser(description='Reverse a file') parser.add_argument( "filename", action="store", help="the file to read") parser.add_argument( "--limit", "-l", action="store", type=int, help="the number of lines to read" ) parser.add_argument( "--version", "-v", action="version", version="%(prog)s 1.0" ) args = parser.parse_args() print(args)

Этот скрипт нельзя выполнить без обязательного аргумента filename

Помимо случая, когда вызов идёт с флагом --version

python reverse_file.py --version

reverse_file.py 1.0

Но можно спокойно выполнить скрипт не передав --limit

python reverse_file.py sites.txt

Namespace(filename='sites.txt', limit=None)

По умолчанию --limit получил None.

Про --version в Namespace нет упоминаний.

--version это не обычный аргумент, потому что имеет опцию action="version" а не action="store" как у .

Передать необязательный аргумент --limit можно следующим оригинальным образом:

python reverse_file.py sites.txt -l 10

Namespace(filename='sites.txt', limit=10)

Можно передать --limit строкой, если она конвертируема в int

python reverse_file.py sites.txt -l "5"

Namespace(filename='sites.txt', limit=5)

Если аргумент не конвертируем в int получим ошибку

python reverse_file.py sites.txt -l "a"

usage: reverse_file.py [-h] [--limit LIMIT] [--version] filename reverse_file.py: error: argument --limit/-l: invalid int value: 'a'

Добавим в скрипт функционал перезаписывающий файл задом-наперёд.

import argparse parser = argparse.ArgumentParser(description='Reverse a file') parser.add_argument("filename", help="file to read") parser.add_argument("--limit", "-l", type=int, help="number of lines to read" ) parser.add_argument( "--version", "-v", action="version", version="%(prog)s 1.0" ) args = parser.parse_args() with open(args.filename) as f: lines = f.readlines() lines.reverse() if args.limit: lines = lines[:args.limit] for line in lines: print(line.strip()[::-1])

Обратите внимание, на то, что переданные аргументы после парсинга доступны как атрибуты объекта args

Применим скрипт к файлу sites.txt

andreyolegovich.ru aredel.com devhops.site heihei.ru testsetup.site topbicycle.ru

python reverse_file.py sites.txt

ur.elcycibpot ur.putestset ur.iehieh ur.spohved moc.ledera ur.hcivogeloyerdna

Применим флаг --limit

python reverse_file.py sites.txt --limit 3

ur.elcycibpot ur.putestset ur.iehieh

Теперь, когда мы хотим прочитать настоящий файл, можно столкнуться с его отсутствием

python reverse_file.py none.txt

Traceback (most recent call last): File "C:\python\reverse_file_form.py", line 14, in <module> with open(args.filename) as f: ^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'none.txt'

Желательно заранее подготовиться к этому с помощью явной проверки существования файла или try

Про менеджер контекста (with) можете прочитать здесь

import argparse parser = argparse.ArgumentParser(description='Reverse a file') parser.add_argument("filename", help="file to read") parser.add_argument("--limit", "-l", type=int, help="number of lines to read" ) parser.add_argument( "--version", "-v", action="version", version="%(prog)s 1.0" ) args = parser.parse_args() try: f = open(args.filename) except FileNotFoundError as e: print(e) else: with f: lines = f.readlines() lines.reverse() if args.limit: lines = lines[:args.limit] for line in lines: print(line.strip()[::-1])

python reverse_file.py none.txt

[Errno 2] No such file or directory: 'none.txt'

Exit Statuses

В предыдущей главе мы обрабатывали ошибку с отсутствующим файлом с помощью try

python reverse_file.py none.txt

[Errno 2] No such file or directory: 'none.txt'

Файл не найден, но скрипт выполнился корректно. Если мы сейчас проверим статус он будет успешным.

Например, в PowerShell :

echo $?

True

В Bash

echo $?

0

Нас может не устраивать такой результат. Например, нужно чтобы CI скрипт вызвавший наш модуль получил явный статус об ошибке и сделал соответствующие действия.

Для решения этой задачи используют sys.exit

import argparse import sys parser = argparse.ArgumentParser(description='Reverse a file') parser.add_argument("filename", help="file to read") parser.add_argument("--limit", "-l", type=int, help="number of lines to read" ) parser.add_argument( "--version", "-v", action="version", version="%(prog)s 1.0" ) args = parser.parse_args() try: f = open(args.filename) except FileNotFoundError as e: print(e) sys.exit(2) else: with f: lines = f.readlines() lines.reverse() if args.limit: lines = lines[:args.limit] for line in lines: print(line.strip()[::-1])

python reverse_file.py none.txt

[Errno 2] No such file or directory: 'none.txt'

echo $?

False

echo $?

2

Значения по умолчанию

Чтобы задать значения по умолчанию используется аргумент default.

Пример скрипта, который получает IP адрес и порт для выполнения запроса, но также имеет дефолтные значения.

import requests import argparse parser = argparse.ArgumentParser() parser.add_argument("--ip", nargs="?", default="10.8.1.101", type=str) parser.add_argument("--port", nargs="?", default="5000", type=str) args = parser.parse_args() r = requests.get(f"http://{args.ip}:{args.port}/health") print(r.status_code)

Article author: Andrei Olegovich

Banner image