Страницы

Поиск по вопросам

пятница, 31 января 2020 г.

Как объявить, описать и проверить параметры командной строки в Python?

#python #python_3x #powershell #argv


В PowerShell есть замечательная возможность объявления параметров скрипта, их проверки,
и автоматического описания. Записывается так:



[CmdletBinding()]
Param(
    # Имя адаптера, обязательное, явно задавить имя параметра не нужно, определяется
по позиции.
    [Parameter(Mandatory=$True,Position=1)]
    [string]$AdapterName,

    # Целевые адреса в виде адрес/маска, например 192.168.72.0/24
    [Parameter(Mandatory=$True)]
    [string[]]$TargetNets
)


А также с проверкой:

[ValidateSet("Tom","Dick","Jane")]
[String]$Name,

[ValidateRange(21,65)]
[Int]$Age,

[ValidateScript({Test-Path $_ -PathType 'Container'})]
[string]$Path


При ошибке ввода оболочка подскажет что и где и как должно быть. Подробнее тут и
тут. Param также применим к функциям.
Есть ли нечто подобное в Python?
    


Ответы

Ответ 1



Пример кода, который показывает как реализовать оба примера из вопроса, используя argparse: #!/usr/bin/env python3 import argparse import pathlib def valid_dir(path): path = pathlib.Path(path) try: if path.is_dir(): return path except OSError: pass raise argparse.ArgumentTypeError('%s is not a valid directory' % (path,)) parser = argparse.ArgumentParser() parser.add_argument('adapter-name') parser.add_argument('targetnets', nargs='+') parser.add_argument('--name', choices={"Tom", "Dick", "Jane"}) parser.add_argument('--age', type=int, choices=range(21, 65)) parser.add_argument('--path', type=valid_dir) print(parser.parse_args()) См. directory path types with argparse. Пример запуска: $ python3 . lo 10.0.3.0/24 127.0.0.0/8 --name Tom --age 22 --path . Namespace(adapter-name='lo', age=22, name='Tom', path=PosixPath('.'), targetnets=['10.0.3.0/24', '127.0.0.0/8'])

Ответ 2



Существует множество библиотек, которые позволяют описывать/проверять параметры командной строки как часть их работы, например, уже упомянутый модуль argparse из стандартной библиотеки, простой docopt, ориентированный на создание стандартного интерфейса, click, ориентированный на поддержку произвольного CLI интерфейса. Парсер командной строки задаётся в docopt, используя стандартный язык для примеров использования, первый фрагмент транслируется в: Usage: ваша-программа ... Это буквальное описание говорит docopt, что ваша-программа имеет два обязательных параметра и второй параметр это список (одно или более значений), живой пример. Вот полный пример кода c docopt: #!/usr/bin/env python3 """Print adapter ips that belong to given networks. Usage: ваша-программа ... ваша-программа -h | --help | --version Arguments: adapter-name Имя адаптера nets Адреса в виде адрес/маска, например 192.168.72.0/24 """ import ipaddress import docopt # $ pip install docopt import netifaces # $ pip install netifaces def parse_ip(ip_string): """Parse ip string such as '10.0.3.0' returning None on error.""" try: return ipaddress.ip_address(ip_string) except ValueError: return None if __name__ == "__main__": args = docopt.docopt(__doc__, version='ваша-программа 1.0') try: networks = [ipaddress.ip_network(network_spec) for network_spec in args['']] ifaddresses = [netifaces.ifaddresses(args[''])[family] for family in [netifaces.AF_INET, netifaces.AF_INET6]] except ValueError as e: raise docopt.DocoptExit(str(e)) adapter_ips = [parse_ip(ip['addr']) for ips in ifaddresses for ip in ips] # find intersection print(*[ip for ip in filter(None, adapter_ips) for network in networks if ip in network]) Пример запуска: $ ваша-программа lo 10.0.3.0/24 127.0.0.0/8 127.0.0.1 $ ваша-программа lxcbr0 10.0.3.0/24 127.0.0.0/8 10.0.3.1 Спецификация, из которой docopt создаёт парсер для командной строки, приведена в __doc__ (строка наверху модуля). Проверка в сам docopt не входит. Как показывает пример, это не всегда нужно: ipaddress.ip_network(), netifaces.ifaddresses() выбросят ValueError, если неправильные значения указать. По желанию, можно установить разные проверяльщики. Вот пример кода с schema. Второй фрагмент c использованием schema: #!/usr/bin/env python3 """Usage: prog.py [--name= --age= --path=]""" import pathlib import docopt # $ pip install docopt from schema import And, Or, Schema, SchemaError, Use # $ pip install schema def valid_dir(path): try: return path.is_dir() except OSError: return False if __name__ == '__main__': args = docopt.docopt(__doc__) schema = Schema({ '--name': Or(None, "Tom", "Dick", "Jane", error='--name can be only Tom, Dick, or Jane'), '--age': Or(None, And(Use(int), lambda n: 21 <= n < 65), error='--age should be in [21, 65) range'), '--path': Or(None, And(Use(pathlib.Path), valid_dir), error='--path should be a valid folder')}) try: args = schema.validate(args) except SchemaError as e: raise docopt.DocoptExit(str(e)) print(args) Пример: $ python prog.py --path . --age 21 Результат: {'--age': 21, '--name': None, '--path': PosixPath('.')} Пример кода без использования schema: #!/usr/bin/env python3 """Usage: prog.py [--name= --age= --path=]""" import pathlib import docopt # $ pip install docopt if __name__ == '__main__': args = docopt.docopt(__doc__) if (args['--name'] is not None and args['--name'] not in {"Tom", "Dick", "Jane"}): raise docopt.DocoptExit('--name can be only Tom, Dick, or Jane') if args['--age'] is not None: try: args['--age'] = int(args['--age']) if args['--age'] not in range(21, 65): raise ValueError except ValueError: raise docopt.DocoptExit('--age should be in [21, 65) range') if args['--path'] is not None: args['--path'] = pathlib.Path(args['--path']) try: if not args['--path'].is_dir(): raise ValueError except (ValueError, OSError): raise docopt.DocoptExit('--path should be a valid folder') print(args) Результат тот же. В данном простом случае, где код ничего кроме проверки аргументов не делает, ограничения на аргументы простые и сами аргументы являются независимыми, вариант с декларативной проверкой аргументов (со schema) выглядит более предпочтительным по сравнению с явным кодом на Питоне, но аргумент, представленный по ссылке в вопросе, является спорным. Это аргумент людей, которые верят, что отсутствие обязательных статических меток для типов в Питоне (но см. typing), должно приводить к куче if isinstance(arg1, type1): raise ValueError, чтобы скомпенсировать работу компилятора в статических языках. Фактически, много вызовов isinstance() не одобряется в Питоне и обычно можно найти лучше решения в зависимости от конкретной задачи. В обычном коде в любом случае, если вы хотите использовать значения, то вам придётся их преобразовывать к нужному виду и обрабатывать соответствующие ошибки по месту использования, чтобы избежать race, например, директория может быть удалена после проверки, но до использования (LBYL vs. EAFP). Использование инструментов по описанию требований к параметрам либо может быть недостаточно для более сложных случаев или же в лучшем случае такое описание будет эквивалентно Python коду только с менее привычным синтаксисом (если вы можете описать любые требования, то ваш язык описаний Turing complete и у нас уже есть приятный Turing complete язык и он называется Python и почему бы его и не использовать напрямую, чтобы выразить требования).

Ответ 3



Используйте argparse import argparse parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument("foo", ..., required=True) parser.parse_args() Ответ на английском. P.S. Кто-то отвечал, но удалился...

Комментариев нет:

Отправить комментарий