Аргументы командной строки Python
Оглавление
- Интерфейс Командной Строки
- Наследие C
- Две утилиты из мира Unix
- Массив sys.argv
- Анатомия аргументов командной строки Python
- Несколько методов анализа аргументов командной строки Python
- Несколько методов проверки аргументов командной строки Python
- Стандартная Библиотека Python
- Несколько Внешних Пакетов Python
- Заключение
- Дополнительные Ресурсы
Добавление возможности обработки аргументов командной строки Python обеспечивает удобный интерфейс для вашей текстовой программы командной строки. Это похоже на то, что представляет собой графический пользовательский интерфейс для визуального приложения, управляемого графическими элементами или виджетами.
Python предоставляет механизм для захвата и извлечения ваших аргументов командной строки Python. Эти значения можно использовать для изменения поведения программы. Например, если ваша программа обрабатывает данные, считанные из файла, то вы можете передать имя файла в свою программу, вместо того чтобы жестко кодировать значение в исходном коде.
К концу этого урока вы будете знать:
- Происхождение аргументов командной строки Python
- Базовая поддержка аргументов командной строки Python
- Стандарты, определяющие разработку интерфейса командной строки
- Основы ручной настройки и обработки аргументов командной строки Python
- Библиотеки, доступные в Python, облегчают разработку сложного интерфейса командной строки
Если вам нужен удобный способ ввода аргументов командной строки Python в вашу программу без импорта специальной библиотеки, или если вы хотите лучше понять общую основу существующих библиотек, предназначенных для создания интерфейса командной строки Python, продолжайте читаю!
Бесплатный бонус: 5 Размышления о мастерстве владения Python - бесплатный курс для разработчиков Python, который показывает вам план действий и мышление, с которым вы будете работать. вам нужно поднять свои навыки работы с Python на новый уровень.
Интерфейс командной строки
А интерфейс командной строки (CLI) предоставляет пользователю возможность взаимодействовать с программой, работающей в текстовом формате. оболочка интерпретатор. Некоторыми примерами интерпретаторов командной строки являются Bash в Linux или Командная строка в Windows. Интерфейс командной строки поддерживается интерпретатором командной строки, который предоставляет командную строку. Его можно охарактеризовать следующими элементами:
- Команда или программа
- Ноль или более аргументов командной строки
- Вывод , представляющий результат выполнения команды
- Текстовая документация, называемая использование или справка
Не каждый интерфейс командной строки может содержать все эти элементы, но и этот список не является исчерпывающим. Сложность командной строки варьируется от возможности передачи одного аргумента до множества аргументов и опций, очень похожих на Язык, зависящий от предметной области. Например, некоторые программы могут запускать веб-документацию из командной строки или интерактивный интерпретатор оболочки, такой как Python.
Два следующих примера с командой Python иллюстрируют описание интерфейса командной строки:
$ python -c "print('Real Python')"
Real Python
В этом первом примере интерпретатор Python использует параметр -c для команды, которая указывает на выполнение аргументов командной строки Python, следующих за параметром -c как программа на Python.
В другом примере показано, как вызвать Python с помощью -h для отображения справки:
$ python -h
usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-b : issue warnings about str(bytes_instance), str(bytearray_instance)
and comparing bytes/bytearray with str. (-bb: issue errors)
[ ... complete help text not shown ... ]
Попробуйте это в своем терминале, чтобы ознакомиться с полной справочной документацией.
Наследие языка Си
Аргументы командной строки Python напрямую наследуются от языка программирования C. Как писал Гвидо Ван Россум в книге "Введение в Python для Unix/C программистов" в 1993 году, C оказал сильное влияние на Python. Гвидо упоминает определения литералов, идентификаторов, операторов и утверждений, таких как break, continue, или return. Использование аргументов командной строки Python также находится под сильным влиянием языка Си.
Чтобы проиллюстрировать сходство, рассмотрим следующую программу на языке Си:
1// main.c
2#include <stdio.h>
3
4int main(int argc, char *argv[]) {
5 printf("Arguments count: %d\n", argc);
6 for (int i = 0; i < argc; i++) {
7 printf("Argument %6d: %s\n", i, argv[i]);
8 }
9 return 0;
10}
Строка 4 определяет main(), , которая является точкой входа в программу на Си. Обратите внимание на параметры:
argcэто целое число, представляющее количество аргументов программы.argvпредставляет собой массив указателей на символы, содержащие название программы в первом элементе массива, за которыми следуют аргументы программы, если таковые имеются, в остальных элементах массива.
Вы можете скомпилировать приведенный выше код в Linux с помощью gcc -o main main.c, затем выполнить с помощью ./main, чтобы получить следующее:
$ gcc -o main main.c
$ ./main
Arguments count: 1
Argument 0: ./main
Если это явно не указано в командной строке с помощью параметра -o, a.out - это имя исполняемого файла по умолчанию, сгенерированного компилятором gcc. Он расшифровывается как вывод на ассемблере и напоминает исполняемые файлы, которые были созданы в старых системах UNIX. Обратите внимание, что имя исполняемого файла ./main является единственным аргументом.
Давайте оживим этот пример, передав несколько аргументов командной строки Python в ту же программу:
$ ./main Python Command Line Arguments
Arguments count: 5
Argument 0: ./main
Argument 1: Python
Argument 2: Command
Argument 3: Line
Argument 4: Arguments
Выходные данные показывают, что количество аргументов равно 5, а список аргументов включает название программы main, за которым следует каждое слово фразы "Python Command Line Arguments", которая вы перешли в командную строку.
Примечание: argc означает количество аргументов, в то время как argv означает вектор аргументов. Чтобы узнать больше, ознакомьтесь с Небольшим обзором C Primer / Аргументов командной строки C.
При компиляции main.c предполагается, что вы использовали систему Linux или Mac OS. В Windows вы также можете скомпилировать эту программу на C одним из следующих вариантов:
- Подсистема Windows для Linux (WSL)): Она доступна в нескольких дистрибутивах Linux, таких как Ubuntu, openSUSE и Debian, среди прочих. Вы можете установить его из магазина Microsoft Store.
- Инструменты сборки Windows: Это включает в себя средства сборки из командной строки Windows, компилятор Microsoft C/C++
cl.exe, и интерфейс компилятора с именемclang.exeдля C/C++. - Microsoft Visual Studio: Это основная интегрированная среда разработки Microsoft (IDE). Чтобы узнать больше о IDE, которые можно использовать как для Python, так и для C в различных операционных системах, включая Windows, ознакомьтесь с Python IDE и редакторами кода (Руководство).
- проект mingw-64: Это поддерживает Компилятор GCC на Окна.
Если вы установили Microsoft Visual Studio или средства сборки Windows, то вы можете скомпилировать main.c следующим образом:
C:/>cl main.c
Вы получите исполняемый файл с именем main.exe, с которого можно начать:
C:/>main
Arguments count: 1
Argument 0: main
Вы могли бы реализовать программу на Python, main.py, которая эквивалентна программе на C, main.c, которую вы видели выше:
# main.py
import sys
if __name__ == "__main__":
print(f"Arguments count: {len(sys.argv)}")
for i, arg in enumerate(sys.argv):
print(f"Argument {i:>6}: {arg}")
Вы не видите argc переменную, как в примере с кодом на C. В Python ее нет, потому что достаточно sys.argv. Вы можете проанализировать аргументы командной строки Python в sys.argv, не зная длины списка, и вы можете вызвать встроенный len(), если количество аргументов необходимо вашей программе.
Также обратите внимание, что enumerate(), при применении к iterable возвращает объект enumerate, который может генерировать пары, связывающие индекс элемента в sys.arg с его соответствующим значением. Это позволяет циклически просматривать содержимое sys.argv без необходимости поддерживать счетчик для индекса в списке.
Выполните main.py следующим образом:
$ python main.py Python Command Line Arguments
Arguments count: 5
Argument 0: main.py
Argument 1: Python
Argument 2: Command
Argument 3: Line
Argument 4: Arguments
sys.argv содержит ту же информацию, что и в программе на C:
- Название программы
main.pyявляется первым элементом списка. - Аргументы
Python,Command,Line, иArgumentsявляются оставшимися элементами в списке.
Благодаря этому краткому введению в несколько тайных аспектов языка Си, вы теперь вооружены некоторыми ценными знаниями для дальнейшего понимания аргументов командной строки Python.
Две утилиты из мира Unix
Чтобы использовать аргументы командной строки Python, в этом руководстве вы частично реализуете некоторые функции двух утилит из экосистемы Unix:
В следующих разделах вы немного познакомитесь с этими инструментами Unix.
sha1sum
sha1sum вычисляет SHA-1 хэши и часто используется для проверки целостности файлов. Для заданных входных данных хэш-функция всегда возвращает одно и то же значение. Любые незначительные изменения во входных данных приведут к получению другого хэш-значения. Прежде чем использовать утилиту с конкретными параметрами, вы можете попробовать отобразить справку:
$ sha1sum --help
Usage: sha1sum [OPTION]... [FILE]...
Print or check SHA1 (160-bit) checksums.
With no FILE, or when FILE is -, read standard input.
-b, --binary read in binary mode
-c, --check read SHA1 sums from the FILEs and check them
--tag create a BSD-style checksum
-t, --text read in text mode (default)
-z, --zero end each output line with NUL, not newline,
and disable file name escaping
[ ... complete help text not shown ... ]
Отображение справки программы командной строки - это обычная функция, доступная в интерфейсе командной строки.
Чтобы вычислить значение хэша SHA-1 для содержимого файла, выполните следующие действия:
$ sha1sum main.c
125a0f900ff6f164752600550879cbfabb098bc3 main.c
В результате в качестве первого поля будет указано значение хэша SHA-1, а в качестве второго - имя файла. Команда может принимать в качестве аргументов более одного файла:
$ sha1sum main.c main.py
125a0f900ff6f164752600550879cbfabb098bc3 main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4 main.py
Благодаря функции расширения подстановочных знаков в терминале Unix, также возможно предоставлять аргументы командной строки Python с подстановочными знаками. Одним из таких символов является звездочка (*):
$ sha1sum main.*
3f6d5274d6317d580e2ffc1bf52beee0d94bf078 main.c
f41259ea5835446536d2e71e566075c1c1bfc111 main.py
Оболочка преобразует main.* в main.c и main.py, которые являются двумя файлами, соответствующими шаблону main.* в текущем каталоге, и передает их в sha1sum. Программа вычисляет Хэш SHA1 для каждого из файлов в списке аргументов. Вы увидите, что в Windows поведение отличается. В Windows нет расширения с помощью подстановочных знаков, поэтому программе, возможно, придется приспособиться к этому. В вашей реализации может потребоваться внутреннее расширение подстановочных знаков.
Без каких-либо аргументов sha1sum считывается из стандартного ввода. Вы можете ввести данные в программу, набрав символы на клавиатуре. Вводимые данные могут содержать любые символы, включая символ возврата каретки Введите. Чтобы завершить ввод, вы должны подать сигнал конец файла с помощью Enter, за которым следует последовательность Ctrl+<класс kbd="ключ-d">D:
1$ sha1sum
2Real
3Python
487263a73c98af453d68ee4aab61576b331f8d9d6 -
Сначала вы вводите название программы, sha1sum, затем Вводите, а затем Real и Python, за каждым также следует Введите. Чтобы закрыть входной поток, вы набираете Ctrl+D. Результатом является значение хэша SHA1, сгенерированного для текста Real\nPython\n. Имя файла - -. Это условное обозначение стандартного ввода. Значение хэша остается неизменным при выполнении следующих команд:
$ python -c "print('Real\nPython\n', end='')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -
$ python -c "print('Real\nPython')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -
$ printf "Real\nPython\n" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -
Далее вы прочтете краткое описание seq.
seq
seq генерирует последовательность чисел. В самом простом виде, например, при генерации последовательности от 1 до 5, вы можете выполнить следующее:
$ seq 5
1
2
3
4
5
Чтобы получить общее представление о возможностях, предоставляемых seq, вы можете просмотреть справку в командной строке:
$ seq --help
Usage: seq [OPTION]... LAST
or: seq [OPTION]... FIRST LAST
or: seq [OPTION]... FIRST INCREMENT LAST
Print numbers from FIRST to LAST, in steps of INCREMENT.
Mandatory arguments to long options are mandatory for short options too.
-f, --format=FORMAT use printf style floating-point FORMAT
-s, --separator=STRING use STRING to separate numbers (default: \n)
-w, --equal-width equalize width by padding with leading zeroes
--help display this help and exit
--version output version information and exit
[ ... complete help text not shown ... ]
В этом руководстве вы напишете несколько упрощенных вариантов sha1sum и seq. В каждом примере вы узнаете о различных аспектах или комбинациях функций, связанных с аргументами командной строки Python.
В Mac OS и Linux sha1sum и seq должны быть предустановлены, хотя функции и справочная информация иногда могут незначительно отличаться в разных системах или дистрибутивах. Если вы используете Windows 10, то наиболее удобным способом является запуск sha1sum и seq в среде Linux, установленной на WSL. Если у вас нет доступа к терминалу, предоставляющему стандартные утилиты Unix, то у вас может быть доступ к онлайн-терминалам:
- Создайте бесплатную учетную запись на PythonAnywhere и запустите консоль Bash.
- Создайте временный терминал Bash на repl.it.
Это два примера, и вы можете найти другие.
Массив sys.argv
Прежде чем изучать некоторые общепринятые соглашения и узнавать, как обрабатывать аргументы командной строки Python, вам необходимо знать, что базовая поддержка всех аргументов командной строки Python обеспечивается с помощью sys.argv. В примерах в следующих разделах показано, как обрабатывать аргументы командной строки Python, хранящиеся в sys.argv, и как преодолеть типичные проблемы, возникающие при попытке доступа к ним. Вы узнаете:
- Как получить доступ к содержимому
sys.argv - Как смягчить побочные эффекты глобального характера
sys.argv - Как обрабатывать пробелы в аргументах командной строки Python
- Как обрабатывать ошибки при обращении к аргументам командной строки Python
- Как использовать исходный формат аргументов командной строки Python, передаваемых в байтах
Давайте начнем!
Отображение аргументов
Модуль sys предоставляет массив с именем argv, который включает в себя следующее:
argv[0]содержит название текущей программы на Python.argv[1:], остальная часть списка содержит все без исключения аргументы командной строки Python, передаваемые программе.
Следующий пример демонстрирует содержимое sys.argv:
1# argv.py
2import sys
3
4print(f"Name of the script : {sys.argv[0]=}")
5print(f"Arguments of the script : {sys.argv[1:]=}")
Вот как работает этот код:
- Строка 2 импортирует внутренний модуль Python
sys. - Строка 4 извлекает название программы, обращаясь к первому элементу списка
sys.argv. - Строка 5 отображает аргументы командной строки Python, извлекая все остальные элементы списка
sys.argv.
Примечание: Синтаксис f-string, используемый в argv.py, использует новый спецификатор отладки в Python 3.8. Чтобы узнать больше об этой новой функции f-string и других, ознакомьтесь с Интересными новыми функциями в Python. 3.8.
Если ваша версия Python меньше 3.8, просто уберите знак равенства (=) в обеих f-строках, чтобы программа могла успешно выполняться. В выходных данных будут отображаться только значения переменных, а не их имена.
Выполните приведенный выше скрипт argv.py со списком произвольных аргументов следующим образом:
$ python argv.py un deux trois quatre
Name of the script : sys.argv[0]='argv.py'
Arguments of the script : sys.argv[1:]=['un', 'deux', 'trois', 'quatre']
Вывод подтверждает, что содержимое sys.argv[0] является скриптом на Python argv.py и что остальные элементы списка sys.argv содержат аргументы скрипта, ['un', 'deux', 'trois', 'quatre'].
Подводя итог, sys.argv содержит все argv.py Аргументы командной строки Python. Когда интерпретатор Python выполняет программу на Python, он анализирует командную строку и заполняет sys.argv аргументами.
Отменяем первый аргумент
Теперь, когда у вас есть достаточный опыт работы с sys.argv, вы будете оперировать аргументами, переданными в командной строке. В примере reverse.py отменяется первый аргумент, переданный в командной строке:
1# reverse.py
2
3import sys
4
5arg = sys.argv[1]
6print(arg[::-1])
В reverse.py процесс обращения первого аргумента вспять выполняется следующими шагами:
- Строка 5 извлекает первый аргумент программы, хранящийся в индексе
1изsys.argv. Помните, что название программы хранится с индексом0вsys.argv. - Строка 6 выводит перевернутую строку.
args[::-1]это способ на языке Python использовать операцию среза для переворачивания списка.
Вы выполняете скрипт следующим образом:
$ python reverse.py "Real Python"
nohtyP laeR
Как и ожидалось, reverse.py работает с "Real Python" и изменяет единственный аргумент на выходной "nohtyP laeR". Обратите внимание, что заключение многословной строки "Real Python" в кавычки гарантирует, что интерпретатор обработает ее как уникальный аргумент, а не как два аргумента. Вы познакомитесь с разделителями аргументов в следующем разделе .
Мутирующий sys.argv
sys.argv является ли глобально доступным для вашей запущенной программы на Python. Все модули, импортированные во время выполнения процесса, имеют прямой доступ к sys.argv. Такой глобальный доступ может быть удобен, но sys.argv не является неизменяемым. Возможно, вам захочется реализовать более надежный механизм предоставления программных аргументов различным модулям в вашей программе на Python, особенно в сложной программе с несколькими файлами.
Понаблюдайте, что произойдет, если вы вмешаетесь в sys.argv:
# argv_pop.py
import sys
print(sys.argv)
sys.argv.pop()
print(sys.argv)
Вы вызываете .pop(), чтобы удалить и вернуть последний элемент в sys.argv.
Выполните приведенный выше сценарий:
$ python argv_pop.py un deux trois quatre
['argv_pop.py', 'un', 'deux', 'trois', 'quatre']
['argv_pop.py', 'un', 'deux', 'trois']
Обратите внимание, что четвертый аргумент больше не включен в sys.argv.
В коротком скрипте вы можете смело полагаться на глобальный доступ к sys.argv, но в более крупной программе вы можете захотеть сохранить аргументы в отдельной переменной. Предыдущий пример можно было бы изменить следующим образом:
# argv_var_pop.py
import sys
print(sys.argv)
args = sys.argv[1:]
print(args)
sys.argv.pop()
print(sys.argv)
print(args)
На этот раз, хотя sys.argv потерял свой последний элемент, args был надежно сохранен. args не является глобальным, и вы можете использовать его для анализа аргументов в соответствии с логикой ваша программа. Менеджер пакетов Python, pip, использует этот подход. Вот короткая выдержка из исходного кода pip:
def main(args=None):
if args is None:
args = sys.argv[1:]
в этом фрагменте кода, взятый из pip исходный код, main() сохраняет в args срез sys.argv, который содержит только аргументы, а не имя файла. sys.argv остается нетронутым, и args не затронуты какие-либо непреднамеренных изменений sys.argv.
Экранирование пробельных символов
В примере reverse.py, который вы видели ранее, первым и единственным аргументом является "Real Python", а результатом - "nohtyP laeR". Аргумент содержит разделитель пробелов между "Real" и "Python", и его необходимо экранировать.
В Linux можно избежать использования пробелов, выполнив одно из следующих действий:
- Заключите аргументы в одинарные кавычки (
') - Заключая аргументы в двойные кавычки (
") - Добавляя к каждому пробелу обратную косую черту (
\)
Без какого-либо из возможных решений reverse.py сохраняет два аргумента: "Real" в sys.argv[1] и "Python" в sys.argv[2]:
$ python reverse.py Real Python
laeR
Приведенный выше вывод показывает, что скрипт изменяет только "Real", а "Python" игнорируется. Чтобы убедиться, что оба аргумента сохранены, вам нужно заключить всю строку в двойные кавычки (").
Вы также можете использовать обратную косую черту (\), чтобы избежать пробелов:
$ python reverse.py Real\ Python
nohtyP laeR
С обратной косой чертой (\), командная оболочка предоставляет уникальный аргумент Python, а затем reverse.py.
В оболочках Unix внутренний разделитель полей (IFS) определяет символы, используемые в качестве разделителей. Содержимое переменной оболочки IFS можно отобразить, выполнив следующую команду:
$ printf "%q\n" "$IFS"
$' \t\n'
Из приведенного выше результата, ' \t\n', вы определяете три разделителя:
- Space (
' ') - Tab (
\t) - Newline (
\n)
Добавление к пробелу обратной косой черты (\) позволяет обойти стандартное использование пробела в качестве разделителя в строке "Real Python". В результате получается один блок текста, как и предполагалось, вместо двух.
Обратите внимание, что в Windows интерпретацией пробелов можно управлять с помощью комбинации двойных кавычек. Это немного противоречит здравому смыслу, потому что в терминале Windows двойная кавычка (") интерпретируется как переключатель для отключения и последующего включения специальных символов, таких как пробел, вкладка или труба (|).
В результате, когда вы заключаете более одной строки в двойные кавычки, терминал Windows интерпретирует первую двойную кавычку как команду для игнорирования специальных символов, а вторую двойную кавычку как команду для интерпретация специальных символов.
Учитывая эту информацию, можно с уверенностью предположить, что заключение более чем одной строки в двойные кавычки приведет к ожидаемому поведению, которое заключается в представлении группы строк в качестве одного аргумента. Чтобы убедиться в этом необычном эффекте использования двойных кавычек в командной строке Windows, обратите внимание на следующие два примера:
C:/>python reverse.py "Real Python"
nohtyP laeR
В приведенном выше примере вы можете интуитивно сделать вывод, что "Real Python" интерпретируется как единственный аргумент. Однако, обратите внимание, что происходит, когда вы используете одиночные двойные кавычки:
C:/>python reverse.py "Real Python
nohtyP laeR
Командная строка передает всю строку "Real Python" в качестве единственного аргумента таким же образом, как если бы аргументом было "Real Python". На самом деле командная строка Windows рассматривает уникальную двойную кавычку как ключ, позволяющий отключить использование пробелов в качестве разделителей, и передает все, что следует за двойной кавычкой, в качестве уникального аргумента.
Для получения дополнительной информации о влиянии двойных кавычек в терминале Windows ознакомьтесь с Более понятным способом заключения в кавычки и экранирования аргументов командной строки Windows..
Ошибки обработки
Аргументами командной строки Python являются свободные строки. Многое может пойти не так, поэтому рекомендуется предоставить пользователям вашей программы некоторые рекомендации на случай, если они передадут неверные аргументы в командной строке. Например, reverse.py ожидает один аргумент, и если вы его опустите, то получите сообщение об ошибке:
1$ python reverse.py
2Traceback (most recent call last):
3 File "reverse.py", line 5, in <module>
4 arg = sys.argv[1]
5IndexError: list index out of range
Возникает исключение Python IndexError, и соответствующая обратная трассировка показывает, что ошибка вызвана выражением arg = sys.argv[1]. Сообщение об исключении будет list index out of range. Вы не передали аргумент в командной строке, поэтому в списке sys.argv по индексу ничего нет 1.
Это распространенный шаблон, с которым можно справиться несколькими различными способами. Для этого начального примера мы постараемся сделать его кратким, включив выражение arg = sys.argv[1] в блок try. Измените код следующим образом:
1# reverse_exc.py
2
3import sys
4
5try:
6 arg = sys.argv[1]
7except IndexError:
8 raise SystemExit(f"Usage: {sys.argv[0]} <string_to_reverse>")
9print(arg[::-1])
Выражение в строке 4 включено в блок try. В строке 8 возникает встроенное исключение SystemExit. Если в reverse_exc.py не передается ни один аргумент, то процесс завершается с кодом состояния 1 после вывода данных об использовании. Обратите внимание на включение sys.argv[0] в сообщение об ошибке. В сообщении об использовании отображается название программы. Теперь, когда вы запускаете ту же программу без каких-либо аргументов командной строки на Python, вы можете увидеть следующий результат:
$ python reverse.py
Usage: reverse.py <string_to_reverse>
$ echo $?
1
reverse.py в командной строке не был передан аргумент. В результате программа выдает SystemExit с сообщением об ошибке. Это приводит к завершению работы программы со статусом 1, который отображается при вводе специальной переменной $? с echo.
Вычисление sha1sum
Вы напишете другой скрипт, чтобы продемонстрировать, что в Unix-подобных системах аргументы командной строки Python передаются байтами из операционной системы. Этот скрипт принимает строку в качестве аргумента и выводит шестнадцатеричный SHA-1 хэш аргумента:
1# sha1sum.py
2
3import sys
4import hashlib
5
6data = sys.argv[1]
7m = hashlib.sha1()
8m.update(bytes(data, 'utf-8'))
9print(m.hexdigest())
Это в некоторой степени вдохновлено sha1sum, но оно намеренно обрабатывает строку, а не содержимое файла. В sha1sum.py шаги для ввода аргументов командной строки Python и вывода результата следующие:
- Строка 6 хранит содержимое первого аргумента в
data. - Строка 7 создает экземпляр алгоритма SHA1.
- Строка 8 обновляет хэш-объект SHA1 содержимым первого аргумента программы. Обратите внимание, что
hash.updateпринимает массив байт в качестве аргумента, поэтому необходимо преобразоватьdataиз строки в массив байт. - В строке 9 выводится шестнадцатеричное представление хэша SHA1, вычисленного в строке 8.
Когда вы запускаете скрипт с аргументом, вы получаете следующее:
$ python sha1sum.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565
Для краткости примера, скрипт sha1sum.py не обрабатывает отсутствующие аргументы командной строки Python. Обработка ошибок может быть решена в этом скрипте так же, как вы это сделали в reverse_exc.py.
Примечание: Оформите заказ hashlib для получения более подробной информации о хэш-функциях, доступных в стандартной библиотеке Python.
Из документации sys.argv вы узнаете, что для получения исходных байтов аргументов командной строки Python вы можете использовать os.fsencode(). Непосредственно получая байты из sys.argv[1], вам не нужно выполнять преобразование строки в байт из data:
1# sha1sum_bytes.py
2
3import os
4import sys
5import hashlib
6
7data = os.fsencode(sys.argv[1])
8m = hashlib.sha1()
9m.update(data)
10print(m.hexdigest())
Основные различия между sha1sum.py и sha1sum_bytes.py выделены в следующих строках:
- Строка 7 заполняет
dataисходными байтами, переданными в аргументы командной строки Python. - Строка 9 передает
dataв качестве аргументаm.update(),, который получает объект, подобный байтам.
Выполните sha1sum_bytes.py для сравнения результатов:
$ python sha1sum_bytes.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565
Шестнадцатеричное значение хэша SHA1 такое же, как и в предыдущем примере sha1sum.py.
Анатомия аргументов командной строки Python
Теперь, когда вы изучили несколько аспектов аргументов командной строки Python, в первую очередь sys.argv, вы собираетесь применить некоторые стандарты, которые регулярно используются разработчиками при реализации интерфейса командной строки.
Аргументы командной строки Python являются подмножеством интерфейса командной строки. Они могут состоять из аргументов различных типов:
- Параметры изменяют поведение определенной команды или программы.
- Аргументы представляют источник или место назначения, подлежащие обработке.
- Подкоманды позволяют программе определять более одной команды с соответствующим набором параметров и аргументов.
Прежде чем вы углубитесь в изучение различных типов аргументов, вы ознакомитесь с общепринятыми стандартами, которые определяют дизайн интерфейса командной строки и аргументов. Они были усовершенствованы с момента появления компьютерного терминала в середине 1960-х годов.
Стандарты
В нескольких доступных стандартах содержатся некоторые определения и рекомендации, способствующие согласованности выполнения команд и их аргументов. Это основные стандарты и ссылки UNIX:
Приведенные выше стандарты определяют руководящие принципы и номенклатуру для всего, что связано с программами и аргументами командной строки Python. Следующие пункты являются примерами, взятыми из этих ссылок:
- POSIX:
- За программой или утилитой следуют параметры, аргументы параметров и операнды.
- Перед всеми параметрами следует ставить знак-разделитель через дефис или минус (
-). - Параметр - аргументы не должны быть необязательными.
- GNU:
- Все программы должны поддерживать две стандартные опции, а именно
--versionи--help. - Опции с длинными именами эквивалентны однобуквенным опциям в стиле Unix. Примером может служить
--debugи-d.
- Все программы должны поддерживать две стандартные опции, а именно
- докоптировать:
- Короткие варианты могут быть объединены в стопку, что означает, что
-abcэквивалентно-a -b -c. - Длинные параметры могут содержать аргументы, указанные после пробела или знака равенства (
=). Длинный параметр--input=ARGэквивалентен--input ARG.
- Короткие варианты могут быть объединены в стопку, что означает, что
Эти стандарты определяют обозначения, которые полезны при описании команды. Аналогичная запись может использоваться для отображения использования конкретной команды, когда вы вызываете ее с параметром -h или --help.
Стандарты GNU очень похожи на стандарты POSIX, но содержат некоторые изменения и расширения. Примечательно, что они добавляют опцию long, которая представляет собой полностью именованную опцию с префиксом из двух дефисов (--). Например, для отображения справки используется обычный параметр -h, а длинный - --help.
Примечание: Вам не нужно строго следовать этим стандартам. Вместо этого следуйте соглашениям, которые успешно использовались в течение многих лет с момента появления UNIX. Если вы пишете набор утилит для себя или своей команды, то убедитесь, что вы придерживаетесь единообразия в использовании различных утилит.
В следующих разделах вы узнаете больше о каждом из компонентов командной строки, параметрах, аргументах и вложенных командах.
Параметры
Параметр , иногда называемый флагом или переключателем, предназначен для изменения поведения программы. Например, команда ls в Linux выводит список содержимого данного каталога. Без каких-либо аргументов она выводит список файлов и каталогов в текущем каталоге:
$ cd /dev
$ ls
autofs
block
bsg
btrfs-control
bus
char
console
Давайте добавим несколько опций. Вы можете объединить -l и -s в -ls, что изменит информацию, отображаемую в терминале:
$ cd /dev
$ ls -ls
total 0
0 crw-r--r-- 1 root root 10, 235 Jul 14 08:10 autofs
0 drwxr-xr-x 2 root root 260 Jul 14 08:10 block
0 drwxr-xr-x 2 root root 60 Jul 14 08:10 bsg
0 crw------- 1 root root 10, 234 Jul 14 08:10 btrfs-control
0 drwxr-xr-x 3 root root 60 Jul 14 08:10 bus
0 drwxr-xr-x 2 root root 4380 Jul 14 15:08 char
0 crw------- 1 root root 5, 1 Jul 14 08:10 console
Параметр может принимать аргумент, который называется параметром-аргументом. Смотрите пример в действии с помощью od ниже:
$ od -t x1z -N 16 main
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 >.ELF............<
0000020
od расшифровывается как восьмеричный сброс. Эта утилита отображает данные в различных печатных форматах, таких как восьмеричный (который используется по умолчанию), шестнадцатеричный, десятичный и ASCII. В приведенном выше примере он берет двоичный файл main и отображает первые 16 байт файла в шестнадцатеричном формате. Параметр -t ожидает тип в качестве параметра-аргумента, а -N ожидает количество входных байт.
В приведенном выше примере -t задан тип x1, который обозначает шестнадцатеричное число и один байт на целое число. За этим следует z для отображения печатаемых символов в конце строки ввода. -N принимает 16 в качестве параметра-аргумента для ограничения количества входных байт до 16.
Аргументы
Аргументы также называются операндами или параметрами в POSIX стандарты. Аргументы представляют собой источник или место назначения данных, с которыми работает команда. Например, команда cp,, которая используется для копирования одного или нескольких файлов в файл или каталог, использует по крайней мере один источник и одну цель:
1$ ls main
2main
3
4$ cp main main2
5
6$ ls -lt
7main
8main2
9...
В строке 4 cp принимает два аргумента:
main: исходный файлmain2: целевой файл
Затем он копирует содержимое main в новый файл с именем main2. И main, и main2 являются аргументами, или операндами, программы cp.
Подкоманды
Концепция подкоманд не задокументирована в стандартах POSIX или GNU, но она присутствует в docopt. Стандартные утилиты Unix - это небольшие инструменты, соответствующие философии Unix. Unix-программы - это программы, которые выполняют одну задачу и делают это хорошо. Это означает, что никаких подкоманд не требуется.
В отличие от этого, программы нового поколения, включая git, go, docker, и gcloud,, имеют несколько иную парадигму, которая включает в себя подкоманды. Они не обязательно являются частью среды Unix, поскольку охватывают несколько операционных систем и развертываются в рамках полноценной экосистемы, требующей нескольких команд.
Возьмем в качестве примера git. Он обрабатывает несколько команд, каждая из которых, возможно, имеет свой собственный набор опций, параметров-аргументов и аргументативных аргументов. Следующие примеры применимы к подкоманде git branch:
git branchотображает ветви локального репозитория git.git branch custom_pythonсоздает локальную ветвьcustom_pythonв локальном репозитории.git branch -d custom_pythonудаляет локальную ветвьcustom_python.git branch --helpотображает справку для подкомандыgit branch.
В экосистеме Python, pip также существует концепция подкоманд. Некоторые pip подкоманды включают list, install, freeze, или uninstall.
Окна
В Windows соглашения, касающиеся аргументов командной строки Python, немного отличаются, в частности, те, которые касаются параметров командной строки. Чтобы подтвердить это различие, возьмите tasklist, который является собственным исполняемым файлом Windows и отображает список запущенных в данный момент процессов. Это похоже на ps в системах Linux или macOS. Ниже приведен пример того, как выполнить tasklist в командной строке Windows:
C:/>tasklist /FI "IMAGENAME eq notepad.exe"
Image Name PID Session Name Session# Mem Usage
========================= ======== ================ =========== ============
notepad.exe 13104 Console 6 13,548 K
notepad.exe 6584 Console 6 13,696 K
Обратите внимание, что разделителем для параметра служит косая черта (/), а не дефис (-), как это принято в системах Unix. Для удобства чтения между названием программы taskslist и параметром /FI есть пробел, но его так же правильно вводить taskslist/FI.
В приведенном выше конкретном примере выполняется tasklist с фильтром, отображающим только запущенные в данный момент процессы Notepad. Вы можете видеть, что в системе есть два запущенных экземпляра процесса Notepad. Хотя это и не эквивалентно, это похоже на выполнение следующей команды в терминале в Unix-подобной системе:
$ ps -ef | grep vi | grep -v grep
andre 2117 4 0 13:33 tty1 00:00:00 vi .gitignore
andre 2163 2134 0 13:34 tty3 00:00:00 vi main.c
Приведенная выше команда ps показывает все текущие запущенные vi процессы. Поведение соответствует Философии Unix, поскольку выходные данные ps преобразуются с помощью двух grep фильтров. Первая команда grep выбирает все вхождения vi, а вторая команда grep сама отфильтровывает вхождение grep.
С распространением инструментов Unix, появляющихся в экосистеме Windows, в Windows также принимаются соглашения, не относящиеся к Windows.
Визуальные эффекты
В начале процесса Python аргументы командной строки Python делятся на две категории:
-
Параметры Python: Они влияют на выполнение интерпретатора Python. Например, добавление опции
-O- это средство оптимизации выполнения программы на Python путем удаления инструкцийassertи__debug__. Существуют и другие Параметры Python, доступные в командной строке. -
Программа на Python и ее аргументы: Следуя параметрам Python (если таковые имеются), вы найдете программу на Python, которая представляет собой имя файла, обычно имеющее расширение
.py, и ее аргументы. По общему правилу, они также могут состоять из вариантов и аргументов.
Выполните следующую команду, предназначенную для выполнения программы main.py, которая принимает параметры и аргументы. Обратите внимание, что в этом примере интерпретатор Python также использует некоторые опции, а именно -B и -v.
$ python -B -v main.py --verbose --debug un deux
В приведенной выше командной строке параметры являются аргументами командной строки Python и расположены следующим образом:
- Параметр
-Bуказывает Python не записывать.pycфайлов при импорте исходных модулей. Для получения более подробной информации о.pycфайлах ознакомьтесь с разделом Что делает компилятор? в Вашем руководстве по исходному коду CPython. - Параметр
-vрасшифровывается как подробный и указывает Python отслеживать все инструкции import. - Аргументы, переданные в
main.py, являются фиктивными и представляют собой два длинных варианта (--verboseи--debug) и два аргумента (unиdeux).
Этот пример аргументов командной строки Python можно проиллюстрировать графически следующим образом:
В программе Python main.py У вас есть доступ только к аргументам командной строки Python, вставленным Python в sys.argv. Параметры Python могут влиять на поведение программы, но недоступны в main.py.
Несколько методов анализа аргументов командной строки Python
Теперь вы познакомитесь с несколькими подходами к пониманию параметров, их аргументов и операндов. Это делается с помощью синтаксического анализааргументов командной строки Python. В этом разделе вы познакомитесь с некоторыми конкретными аспектами аргументов командной строки Python и методами их обработки. Сначала вы увидите пример, в котором представлен простой подход, основанный на понятиях списка для сбора и отделения параметров от аргументов. Тогда у вас получится:
- Используйте регулярные выражения для извлечения элементов из командной строки
- Узнайте как обрабатывать файлы, передаваемые из командной строки
- Воспринимать стандартный ввод таким образом, чтобы он был совместим со средствами Unix
- Отличать обычный вывод программы от ошибок
- Реализовать пользовательский синтаксический анализатор для чтения аргументов командной строки Python
Это послужит подготовкой к настройкам, связанным с модулями в стандартных библиотеках или из внешних библиотек, о которых вы узнаете позже в этом руководстве.
Для чего-то несложного может быть достаточно следующего шаблона, который не требует упорядочивания и не обрабатывает аргументы параметров:
# cul.py
import sys
opts = [opt for opt in sys.argv[1:] if opt.startswith("-")]
args = [arg for arg in sys.argv[1:] if not arg.startswith("-")]
if "-c" in opts:
print(" ".join(arg.capitalize() for arg in args))
elif "-u" in opts:
print(" ".join(arg.upper() for arg in args))
elif "-l" in opts:
print(" ".join(arg.lower() for arg in args))
else:
raise SystemExit(f"Usage: {sys.argv[0]} (-c | -u | -l) <arguments>...")
Цель приведенной выше программы - изменить регистр аргументов командной строки Python. Доступны три варианта:
-cчтобы прописать аргументы с большой буквы-uчтобы преобразовать аргументы в верхний регистр-lчтобы преобразовать аргумент в нижний регистр
Код собирает и разделяет различные типы аргументов, используя список значений:
- В строке 5 собраны все опции путем фильтрации любых аргументов командной строки Python, начинающихся с дефиса (
-). - Строка 6 собирает аргументы программы путем фильтрации параметров.
Когда вы выполняете приведенную выше программу на Python с набором опций и аргументов, вы получаете следующий результат:
$ python cul.py -c un deux trois
Un Deux Trois
Такого подхода может быть достаточно во многих ситуациях, но он не сработает в следующих случаях:
- Если важен порядок, и, в частности, если опции должны отображаться перед аргументами
- Если необходима поддержка параметров-аргументов
- Если некоторые аргументы начинаются с дефиса (
-)
Вы можете использовать другие опции, прежде чем обращаться к библиотеке, подобной argparse или click.
Регулярные выражения
Вы можете использовать регулярное выражение для обеспечения соблюдения определенного порядка, определенных параметров и аргументов-опций или даже типа аргументов. Чтобы проиллюстрировать использование регулярного выражения для анализа аргументов командной строки Python, вы реализуете версию Python seq,, которая представляет собой программу, печатающую последовательность чисел. В соответствии с соглашениями docopt спецификация для seq.py может быть такой:
Print integers from <first> to <last>, in steps of <increment>.
Usage:
python seq.py --help
python seq.py [-s SEPARATOR] <last>
python seq.py [-s SEPARATOR] <first> <last>
python seq.py [-s SEPARATOR] <first> <increment> <last>
Mandatory arguments to long options are mandatory for short options too.
-s, --separator=STRING use STRING to separate numbers (default: \n)
--help display this help and exit
If <first> or <increment> are omitted, they default to 1. When <first> is
larger than <last>, <increment>, if not set, defaults to -1.
The sequence of numbers ends when the sum of the current number and
<increment> reaches the limit imposed by <last>.
Сначала рассмотрим регулярное выражение, которое предназначено для отражения вышеуказанных требований:
1args_pattern = re.compile(
2 r"""
3 ^
4 (
5 (--(?P<HELP>help).*)|
6 ((?:-s|--separator)\s(?P<SEP>.*?)\s)?
7 ((?P<OP1>-?\d+))(\s(?P<OP2>-?\d+))?(\s(?P<OP3>-?\d+))?
8 )
9 $
10""",
11 re.VERBOSE,
12)
Чтобы поэкспериментировать с приведенным выше регулярным выражением, вы можете использовать фрагмент, записанный в Регулярном выражении 101. Регулярное выражение отражает и реализует несколько аспектов требований, приведенных для seq. В частности, команда может принимать следующий вид:
- Вариант справки, в кратком (
-h) или расширенном формате (--help), записывается как с именем группа называетсяHELP - Параметр-разделитель,
-sили--separator, принимающий необязательный аргумент и записываемый как именованная группа с именемSEP - До трех целочисленных операндов, записываемых соответственно как
OP1,OP2, иOP3
Для наглядности в приведенном выше шаблоне args_pattern используется флаг re.VERBOSE в строке 11. Это позволяет растянуть регулярное выражение на несколько строк для удобства чтения. Шаблон подтверждает следующее:
- Порядок аргументации: Ожидается, что параметры и аргументы будут представлены в заданном порядке. Например, параметры ожидаются перед аргументами.
- В качестве опций ожидаются значения параметров**: Только
--help,-s, или--separator. - Взаимоисключающий аргумент: Параметр
--helpнесовместим с другими параметрами или аргументами. - Тип аргумента: Ожидается, что операнды будут положительными или отрицательными целыми числами.
Чтобы регулярное выражение могло обрабатывать такие вещи, оно должно видеть все аргументы командной строки Python в одной строке. Вы можете собрать их, используя str.join.():
arg_line = " ".join(sys.argv[1:])
В результате получается arg_line строка, содержащая все аргументы, кроме имени программы, разделенные пробелом.
Учитывая приведенный выше шаблон args_pattern, вы можете извлечь аргументы командной строки Python с помощью следующей функции:
def parse(arg_line: str) -> Dict[str, str]:
args: Dict[str, str] = {}
if match_object := args_pattern.match(arg_line):
args = {k: v for k, v in match_object.groupdict().items()
if v is not None}
return args
Шаблон уже обрабатывает порядок аргументов, взаимную исключительность между параметрами и аргументами, а также тип аргументов. parse() применяется re.match() в строку аргумента, чтобы извлечь нужные значения и сохранить данные в словаре.
Словарь содержит названия каждой группы в качестве ключей и соответствующие им значения. Например, если значение arg_line равно --help, то значение словаря равно {'HELP': 'help'}. Если arg_line равно -s T 10, то словарь становится {'SEP': 'T', 'OP1': '10'}. Вы можете развернуть приведенный ниже блок кода, чтобы увидеть реализацию seq с регулярными выражениями.
Приведенный ниже код реализует ограниченную версию seq с регулярным выражением для обработки синтаксического анализа и проверки в командной строке:
# seq_regex.py
from typing import List, Dict
import re
import sys
USAGE = (
f"Usage: {sys.argv[0]} [-s <separator>] [first [increment]] last"
)
args_pattern = re.compile(
r"""
^
(
(--(?P<HELP>help).*)|
((?:-s|--separator)\s(?P<SEP>.*?)\s)?
((?P<OP1>-?\d+))(\s(?P<OP2>-?\d+))?(\s(?P<OP3>-?\d+))?
)
$
""",
re.VERBOSE,
)
def parse(arg_line: str) -> Dict[str, str]:
args: Dict[str, str] = {}
if match_object := args_pattern.match(arg_line):
args = {k: v for k, v in match_object.groupdict().items()
if v is not None}
return args
def seq(operands: List[int], sep: str = "\n") -> str:
first, increment, last = 1, 1, 1
if len(operands) == 1:
last = operands[0]
if len(operands) == 2:
first, last = operands
if first > last:
increment = -1
if len(operands) == 3:
first, increment, last = operands
last = last + 1 if increment > 0 else last - 1
return sep.join(str(i) for i in range(first, last, increment))
def main() -> None:
args = parse(" ".join(sys.argv[1:]))
if not args:
raise SystemExit(USAGE)
if args.get("HELP"):
print(USAGE)
return
operands = [int(v) for k, v in args.items() if k.startswith("OP")]
sep = args.get("SEP", "\n")
print(seq(operands, sep))
if __name__ == "__main__":
main()
Вы можете выполнить приведенный выше код, выполнив следующую команду:
$ python seq_regex.py 3
Это должно привести к следующему результату:
1
2
3
Попробуйте использовать эту команду с другими комбинациями, включая опцию --help.
Вы не увидели здесь опции версии. Это было сделано намеренно, чтобы сократить длину примера. Вы можете рассмотреть возможность добавления опции версии в качестве расширенного упражнения. В качестве подсказки, вы могли бы изменить регулярное выражение, заменив строку (--(?P<HELP>help).*)| на (--(?P<HELP>help).*)|(--(?P<VER>version).*)|. Дополнительный блок if также потребуется в main().
На данный момент вы знаете несколько способов извлечения параметров и аргументов из командной строки. До сих пор аргументами командной строки Python были только строки или целые числа. Далее вы узнаете, как обрабатывать файлы, передаваемые в качестве аргументов.
Обработка файлов
Теперь пришло время поэкспериментировать с аргументами командной строки Python, которые, как ожидается, будут именами файлов. Измените sha1sum.py, чтобы обрабатывать один или несколько файлов в качестве аргументов. В итоге вы получите обновленную версию оригинальной утилиты sha1sum, которая принимает один или несколько файлов в качестве аргументов и отображает шестнадцатеричный хэш SHA1 для каждого файла, за которым следует имя файла:
# sha1sum_file.py
import hashlib
import sys
def sha1sum(filename: str) -> str:
hash = hashlib.sha1()
with open(filename, mode="rb") as f:
hash.update(f.read())
return hash.hexdigest()
for arg in sys.argv[1:]:
print(f"{sha1sum(arg)} {arg}")
sha1sum() применяется к данным, считанным из каждого файла, который вы передали в командной строке, а не к самой строке. Обратите внимание, что m.update() принимает в качестве аргумента объект, подобный байтам, и что результат вызова read() после открытия файла в режиме rb вернет bytes объект. Для получения дополнительной информации об обработке содержимого файла ознакомьтесь с Чтение и запись файлов в Python и, в частности, с разделом Работа с байтами.
Эволюция sha1sum_file.py от обработки строк в командной строке к манипулированию содержимым файлов приближает вас к первоначальной реализации sha1sum:
$ sha1sum main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d main
125a0f900ff6f164752600550879cbfabb098bc3 main.c
Выполнение программы на Python с теми же аргументами командной строки на Python приводит к следующему результату:
$ python sha1sum_file.py main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d main
125a0f900ff6f164752600550879cbfabb098bc3 main.c
Поскольку вы взаимодействуете с интерпретатором командной строки или командной строкой Windows, вы также получаете преимущество расширения подстановочных знаков, предоставляемого командной строкой. Чтобы доказать это, вы можете повторно использовать main.py, который отображает каждый аргумент с номером аргумента и его значением:
$ python main.py main.*
Arguments count: 5
Argument 0: main.py
Argument 1: main.c
Argument 2: main.exe
Argument 3: main.obj
Argument 4: main.py
Вы можете видеть, что оболочка автоматически выполняет подстановочное расширение, так что любой файл с базовым именем, совпадающим с main, независимо от расширения, является частью sys.argv.
Расширение подстановочного знака недоступно в Windows. Чтобы получить такое же поведение, вам необходимо реализовать его в своем коде. Чтобы реорганизовать main.py для работы с расширением по шаблону, вы можете использовать glob. Следующий пример работает в Windows, и, хотя он не такой лаконичный, как исходный main.py, один и тот же код ведет себя одинаково на разных платформах:
1# main_win.py
2
3import sys
4import glob
5import itertools
6from typing import List
7
8def expand_args(args: List[str]) -> List[str]:
9 arguments = args[:1]
10 glob_args = [glob.glob(arg) for arg in args[1:]]
11 arguments += itertools.chain.from_iterable(glob_args)
12 return arguments
13
14if __name__ == "__main__":
15 args = expand_args(sys.argv)
16 print(f"Arguments count: {len(args)}")
17 for i, arg in enumerate(args):
18 print(f"Argument {i:>6}: {arg}")
В main_win.py, expand_args используется параметр glob.glob() для обработки подстановочных знаков в стиле shell. Вы можете проверить результат в Windows и любой другой операционной системе:
C:/>python main_win.py main.*
Arguments count: 5
Argument 0: main_win.py
Argument 1: main.c
Argument 2: main.exe
Argument 3: main.obj
Argument 4: main.py
Это устраняет проблему обработки файлов с использованием подстановочных знаков, таких как звездочка (*) или вопросительный знак (?), но как насчет stdin?
Если вы не передаете никаких параметров исходной утилите sha1sum, то она ожидает считывания данных из стандартного ввода. Это текст, который вы вводите в терминале и который заканчивается, когда вы набираете Ctrl+D в Unix-подобных системах или Ctrl+Z в Windows. Эти управляющие последовательности отправляют терминалу сообщение об окончании файла (EOF), которое останавливает чтение с stdin и возвращает введенные данные.
В следующем разделе вы добавите в свой код возможность чтения из стандартного потока ввода.
Стандартный ввод
Когда вы измените предыдущую реализацию Python sha1sum для обработки стандартного ввода с помощью sys.stdin,, вы приблизитесь к оригиналу sha1sum:
# sha1sum_stdin.py
from typing import List
import hashlib
import pathlib
import sys
def process_file(filename: str) -> bytes:
return pathlib.Path(filename).read_bytes()
def process_stdin() -> bytes:
return bytes("".join(sys.stdin), "utf-8")
def sha1sum(data: bytes) -> str:
sha1_hash = hashlib.sha1()
sha1_hash.update(data)
return sha1_hash.hexdigest()
def output_sha1sum(data: bytes, filename: str = "-") -> None:
print(f"{sha1sum(data)} {filename}")
def main(args: List[str]) -> None:
if not args:
args = ["-"]
for arg in args:
if arg == "-":
output_sha1sum(process_stdin(), "-")
else:
output_sha1sum(process_file(arg), arg)
if __name__ == "__main__":
main(sys.argv[1:])
К этой новой версии sha1sum применяются два соглашения:
- Без каких-либо аргументов программа ожидает, что данные будут предоставлены в виде стандартного ввода,
sys.stdin, который представляет собой читаемый файловый объект. - Когда в качестве аргумента файла в командной строке указывается дефис (
-), программа интерпретирует это как чтение файла из стандартного ввода.
Попробуйте этот новый скрипт без каких-либо аргументов. Введите первый афоризм из книги "Дзен Питона", затем завершите ввод сочетанием клавиш Ctrl+D в Unix-подобных системах или Ctrl+Z в Windows:
$ python sha1sum_stdin.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4 -
Вы также можете включить один из аргументов в виде stdin в сочетании с другими аргументами файла, например:
$ python sha1sum_stdin.py main.py - main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4 main.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4 -
125a0f900ff6f164752600550879cbfabb098bc3 main.c
Другой подход к Unix-подобным системам заключается в предоставлении /dev/stdin вместо - для обработки стандартного ввода:
$ python sha1sum_stdin.py main.py /dev/stdin main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4 main.py
Beautiful is better than ugly.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4 /dev/stdin
125a0f900ff6f164752600550879cbfabb098bc3 main.c
В Windows нет эквивалента /dev/stdin, поэтому использование - в качестве аргумента файла работает должным образом.
Скрипт sha1sum_stdin.py не содержит всей необходимой обработки ошибок, но вы узнаете о некоторых недостающих функциях позже в этом руководстве.
Стандартный вывод и стандартная ошибка
Обработка в командной строке может иметь прямое отношение к stdin, чтобы соблюдать соглашения, подробно описанные в предыдущем разделе. Стандартный вывод, хотя и не имеет непосредственного отношения к делу, по-прежнему вызывает беспокойство, если вы хотите придерживаться Философии Unix. Чтобы можно было объединять небольшие программы, вам, возможно, придется учитывать три стандартных потока:
stdinstdoutstderr
Выходные данные одной программы становятся входными данными другой, что позволяет вам объединять небольшие утилиты в цепочку. Например, если вы хотите отсортировать афоризмы из "Дзен Питона", то вы могли бы выполнить следующее:
$ python -c "import this" | sort
Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch.
...
Приведенный выше вывод сокращен для удобства чтения. Теперь представьте, что у вас есть программа, которая выводит те же данные, но также печатает некоторую отладочную информацию:
# zen_sort_debug.py
print("DEBUG >>> About to print the Zen of Python")
import this
print("DEBUG >>> Done printing the Zen of Python")
Выполнение приведенного выше скрипта на Python дает:
$ python zen_sort_debug.py
DEBUG >>> About to print the Zen of Python
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
...
DEBUG >>> Done printing the Zen of Python
Многоточие (...) указывает на то, что выходные данные были сокращены для улучшения читаемости.
Теперь, если вы хотите отсортировать список афоризмов, то выполните команду следующим образом:
$ python zen_sort_debug.py | sort
Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch.
Beautiful is better than ugly.
Complex is better than complicated.
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python
Errors should never pass silently.
...
Возможно, вы понимаете, что не собирались использовать выходные данные отладки в качестве входных данных команды sort. Чтобы устранить эту проблему, вы хотите отправить трассировки в поток стандартных ошибок stderr вместо:
# zen_sort_stderr.py
import sys
print("DEBUG >>> About to print the Zen of Python", file=sys.stderr)
import this
print("DEBUG >>> Done printing the Zen of Python", file=sys.stderr)
Выполните zen_sort_stderr.py, чтобы выполнить следующие действия:
$ python zen_sort_stderr.py | sort
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python
Although never is often better than *right* now.
Although practicality beats purity.
Although that way may not be obvious at first unless you're Dutch
....
Теперь трассировки отображаются на терминале, но они не используются в качестве входных данных для команды sort.
Пользовательские парсеры
Вы можете реализовать seq, используя регулярное выражение, если аргументы не слишком сложны. Тем не менее, шаблон регулярных выражений может быстро усложнить обслуживание скрипта. Прежде чем вы попытаетесь получить помощь от определенных библиотек, другой подход заключается в создании пользовательского синтаксического анализатора. Синтаксический анализатор - это цикл, который извлекает каждый аргумент один за другим и применяет пользовательскую логику, основанную на семантике вашей программы.
Возможная реализация для обработки аргументов seq_parse.py может быть следующей:
1def parse(args: List[str]) -> Tuple[str, List[int]]:
2 arguments = collections.deque(args)
3 separator = "\n"
4 operands: List[int] = []
5 while arguments:
6 arg = arguments.popleft()
7 if not operands:
8 if arg == "--help":
9 print(USAGE)
10 sys.exit(0)
11 if arg in ("-s", "--separator"):
12 separator = arguments.popleft()
13 continue
14 try:
15 operands.append(int(arg))
16 except ValueError:
17 raise SystemExit(USAGE)
18 if len(operands) > 3:
19 raise SystemExit(USAGE)
20
21 return separator, operands
parse() получает список аргументов без имени файла Python и использует collections.deque() для получения преимущества .popleft(),, который удаляет элементы слева от коллекции. По мере того, как элементы списка аргументов будут раскрываться, вы будете применять логику, ожидаемую для вашей программы. В parse() вы можете наблюдать следующее:
- Цикл
whileлежит в основе функции и завершается, когда больше нет аргументов для анализа, когда вызывается справка или когда возникает ошибка. - Если обнаружен параметр
separator, то ожидается, что следующим аргументом будет разделитель. operandsхранит целые числа, которые используются для вычисления последовательности. Должен быть как минимум один операнд и не более трех.
Полная версия кода для parse() доступна ниже:
# seq_parse.py
from typing import Dict, List, Tuple
import collections
import re
import sys
USAGE = (f"Usage: {sys.argv[0]} "
"[--help] | [-s <sep>] [first [incr]] last")
def seq(operands: List[int], sep: str = "\n") -> str:
first, increment, last = 1, 1, 1
if len(operands) == 1:
last = operands[0]
if len(operands) == 2:
first, last = operands
if first > last:
increment = -1
if len(operands) == 3:
first, increment, last = operands
last = last + 1 if increment > 0 else last - 1
return sep.join(str(i) for i in range(first, last, increment))
def parse(args: List[str]) -> Tuple[str, List[int]]:
arguments = collections.deque(args)
separator = "\n"
operands: List[int] = []
while arguments:
arg = arguments.popleft()
if not len(operands):
if arg == "--help":
print(USAGE)
sys.exit(0)
if arg in ("-s", "--separator"):
separator = arguments.popleft() if arguments else None
continue
try:
operands.append(int(arg))
except ValueError:
raise SystemExit(USAGE)
if len(operands) > 3:
raise SystemExit(USAGE)
return separator, operands
def main() -> None:
sep, operands = parse(sys.argv[1:])
if not operands:
raise SystemExit(USAGE)
print(seq(operands, sep))
if __name__ == "__main__":
main()
Обратите внимание, что некоторые аспекты обработки ошибок сведены к минимуму, чтобы примеры были относительно короткими.
Такого ручного разбора аргументов командной строки Python может быть достаточно для простого набора аргументов. Однако при увеличении сложности он быстро становится подвержен ошибкам из-за следующего:
- Большое количество аргументов
- Сложность и взаимозависимость между аргументами
- Проверка для выполнения в соответствии с аргументами
Пользовательский подход не подлежит повторному использованию и требует изобретать велосипед в каждой программе заново. К концу этого руководства вы усовершенствуете это решение, созданное вручную, и освоите несколько более эффективных методов.
Несколько методов проверки аргументов командной строки Python
Вы уже выполнили проверку для аргументов командной строки Python в нескольких примерах, таких как seq_regex.py и seq_parse.py. В первом примере вы использовали регулярное выражение, а во втором примере - пользовательский синтаксический анализатор.
В обоих этих примерах учитывались одни и те же аспекты. Они рассматривали ожидаемые варианты как краткие (-s) или развернутые (--separator). Они учитывали порядок аргументов, чтобы опции не размещались после операндов. Наконец, они рассмотрели тип, целое число для операндов и количество аргументов - от одного до трех аргументов.
Проверка типов с помощью классов данных Python
Ниже приведено доказательство концепции, в котором делается попытка проверить тип аргументов, передаваемых в командной строке. В следующем примере вы проверяете количество аргументов и их соответствующий тип:
# val_type_dc.py
import dataclasses
import sys
from typing import List, Any
USAGE = f"Usage: python {sys.argv[0]} [--help] | firstname lastname age]"
@dataclasses.dataclass
class Arguments:
firstname: str
lastname: str
age: int = 0
def check_type(obj):
for field in dataclasses.fields(obj):
value = getattr(obj, field.name)
print(
f"Value: {value}, "
f"Expected type {field.type} for {field.name}, "
f"got {type(value)}"
)
if type(value) != field.type:
print("Type Error")
else:
print("Type Ok")
def validate(args: List[str]):
# If passed to the command line, need to convert
# the optional 3rd argument from string to int
if len(args) > 2 and args[2].isdigit():
args[2] = int(args[2])
try:
arguments = Arguments(*args)
except TypeError:
raise SystemExit(USAGE)
check_type(arguments)
def main() -> None:
args = sys.argv[1:]
if not args:
raise SystemExit(USAGE)
if args[0] == "--help":
print(USAGE)
else:
validate(args)
if __name__ == "__main__":
main()
Если вы не передадите параметр --help в командной строке, этот скрипт ожидает два или три аргумента:
- Обязательная строка:
firstname - Обязательная строка:
lastname - Необязательное целое число:
age
Поскольку все элементы в sys.argv являются строками, вам необходимо преобразовать необязательный третий аргумент в целое число, если он состоит из цифр. str.isdigit() проверяет, все ли символы в строке являются цифрами. Кроме того, создав класс данных Arguments со значениями преобразованных аргументов, вы получите два подтверждения:
- Если количество аргументов не соответствует количеству обязательных полей, ожидаемому в
Arguments, то вы получите сообщение об ошибке. Это минимум два и максимум три поля. - Если типы после преобразования не соответствуют типам, определенным в определении класса данных
Arguments, то выводится сообщение об ошибке.
Вы можете увидеть это в действии при следующем выполнении:
$ python val_type_dc.py Guido "Van Rossum" 25
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van Rossum, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: 25, Expected type <class 'int'> for age, got <class 'int'>
Type Ok
В приведенном выше примере выполнения количество аргументов указано правильно, и тип каждого аргумента также указан правильно.
Теперь выполните ту же команду, но без третьего аргумента:
$ python val_type_dc.py Guido "Van Rossum"
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van Rossum, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: 0, Expected type <class 'int'> for age, got <class 'int'>
Type Ok
Результат также успешен, поскольку поле age определено со значением по умолчанию, 0, поэтому класс данных Arguments этого не требует.
Напротив, если третий аргумент имеет неправильный тип — скажем, строку вместо целого — то вы получите сообщение об ошибке:
python val_type_dc.py Guido Van Rossum
Value: Guido, Expected type <class 'str'> for firstname, got <class 'str'>
Type Ok
Value: Van, Expected type <class 'str'> for lastname, got <class 'str'>
Type Ok
Value: Rossum, Expected type <class 'int'> for age, got <class 'str'>
Type Error
Ожидаемое значение Van Rossum не заключено в кавычки, поэтому оно разделено. Второе слово фамилии, Rossum, является строкой, которая обрабатывается как возраст, который, как ожидается, будет равен int. Проверка не выполнена.
Примечание: Для получения более подробной информации об использовании классов данных в Python ознакомьтесь с Окончательным руководством по классам данных в Python 3.7.
Аналогично, вы могли бы также использовать NamedTuple для достижения аналогичной проверки. Вы бы заменили класс данных на класс, производный от NamedTuple, а check_type() изменился бы следующим образом:
from typing import NamedTuple
class Arguments(NamedTuple):
firstname: str
lastname: str
age: int = 0
def check_type(obj):
for attr, value in obj._asdict().items():
print(
f"Value: {value}, "
f"Expected type {obj.__annotations__[attr]} for {attr}, "
f"got {type(value)}"
)
if type(value) != obj.__annotations__[attr]:
print("Type Error")
else:
print("Type Ok")
A NamedTuple предоставляет функции, подобные _asdict, которые преобразуют объект в словарь, который можно использовать для поиска данных. Он также предоставляет такие атрибуты, как __annotations__, который представляет собой словарь, в котором хранятся типы для каждого поля, и более подробную информацию о __annotations__ смотрите в Проверка типов в Python (руководство).
Как указано в Руководстве по проверке типов Python (руководство), вы также можете использовать существующие пакеты, такие как Enforce, Pydantic и Pytypes для расширенной проверки.
Пользовательская проверка
В отличие от того, что вы уже изучали ранее, для детальной проверки могут потребоваться некоторые индивидуальные подходы. Например, если вы попытаетесь выполнить sha1sum_stdin.py с неправильным именем файла в качестве аргумента, то получите следующее:
$ python sha1sum_stdin.py bad_file.txt
Traceback (most recent call last):
File "sha1sum_stdin.py", line 32, in <module>
main(sys.argv[1:])
File "sha1sum_stdin.py", line 29, in main
output_sha1sum(process_file(arg), arg)
File "sha1sum_stdin.py", line 9, in process_file
return pathlib.Path(filename).read_bytes()
File "/usr/lib/python3.8/pathlib.py", line 1222, in read_bytes
with self.open(mode='rb') as f:
File "/usr/lib/python3.8/pathlib.py", line 1215, in open
return io.open(self, mode, buffering, encoding, errors, newline,
File "/usr/lib/python3.8/pathlib.py", line 1071, in _opener
return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'bad_file.txt'
bad_file.txt не существует, но программа пытается его прочитать.
Вернитесь к main() в sha1sum_stdin.py, чтобы обработать несуществующие файлы, переданные в командной строке:
1def main(args):
2 if not args:
3 output_sha1sum(process_stdin())
4 for arg in args:
5 if arg == "-":
6 output_sha1sum(process_stdin(), "-")
7 continue
8 try:
9 output_sha1sum(process_file(arg), arg)
10 except FileNotFoundError as err:
11 print(f"{sys.argv[0]}: {arg}: {err.strerror}", file=sys.stderr)
Чтобы увидеть полный пример с этой дополнительной проверкой, разверните блок кода ниже:
# sha1sum_val.py
from typing import List
import hashlib
import pathlib
import sys
def process_file(filename: str) -> bytes:
return pathlib.Path(filename).read_bytes()
def process_stdin() -> bytes:
return bytes("".join(sys.stdin), "utf-8")
def sha1sum(data: bytes) -> str:
m = hashlib.sha1()
m.update(data)
return m.hexdigest()
def output_sha1sum(data: bytes, filename: str = "-") -> None:
print(f"{sha1sum(data)} {filename}")
def main(args: List[str]) -> None:
if not args:
output_sha1sum(process_stdin())
for arg in args:
if arg == "-":
output_sha1sum(process_stdin(), "-")
continue
try:
output_sha1sum(process_file(arg), arg)
except (FileNotFoundError, IsADirectoryError) as err:
print(f"{sys.argv[0]}: {arg}: {err.strerror}", file=sys.stderr)
if __name__ == "__main__":
main(sys.argv[1:])
Когда вы выполняете этот измененный скрипт, вы получаете следующее:
$ python sha1sum_val.py bad_file.txt
sha1sum_val.py: bad_file.txt: No such file or directory
Обратите внимание, что ошибка, отображаемая на терминале, записывается в stderr, поэтому она не влияет на данные, ожидаемые командой, которая считывает выходные данные sha1sum_val.py:
$ python sha1sum_val.py bad_file.txt main.py | cut -d " " -f 1
sha1sum_val.py: bad_file.txt: No such file or directory
d84372fc77a90336b6bb7c5e959bcb1b24c608b4
Эта команда преобразует вывод sha1sum_val.py в cut, чтобы включить только первое поле. Вы можете видеть, что cut игнорирует сообщение об ошибке, поскольку получает только данные, отправленные на stdout.
Стандартная библиотека Python
Несмотря на различные подходы, которые вы использовали для обработки аргументов командной строки Python, любой сложной программе было бы лучше использовать существующие библиотеки для выполнения тяжелой работы, требуемой сложными интерфейсами командной строки. Начиная с версии Python 3.7, в стандартной библиотеке есть три средства анализа командной строки:
Рекомендуемый модуль из стандартной библиотеки - argparse. Стандартная библиотека также предоставляет optparse, но он официально признан устаревшим и упоминается здесь только для вашего сведения. Он был заменен на argparse в Python 3.2, и вы не увидите его обсуждения в этом руководстве.
argparse
Вы собираетесь вернуться к sha1sum_val.py, самому последнему клону sha1sum, чтобы ознакомиться с преимуществами argparse. Для этого вы измените main() и добавите init_argparse для создания экземпляра argparse.ArgumentParser:
1import argparse
2
3def init_argparse() -> argparse.ArgumentParser:
4 parser = argparse.ArgumentParser(
5 usage="%(prog)s [OPTION] [FILE]...",
6 description="Print or check SHA1 (160-bit) checksums."
7 )
8 parser.add_argument(
9 "-v", "--version", action="version",
10 version = f"{parser.prog} version 1.0.0"
11 )
12 parser.add_argument('files', nargs='*')
13 return parser
14
15def main() -> None:
16 parser = init_argparse()
17 args = parser.parse_args()
18 if not args.files:
19 output_sha1sum(process_stdin())
20 for file in args.files:
21 if file == "-":
22 output_sha1sum(process_stdin(), "-")
23 continue
24 try:
25 output_sha1sum(process_file(file), file)
26 except (FileNotFoundError, IsADirectoryError) as err:
27 print(f"{sys.argv[0]}: {file}: {err.strerror}", file=sys.stderr)
За счет добавления нескольких дополнительных строк по сравнению с предыдущей реализацией вы получаете простой подход к добавлению --help и --version опций, которых раньше не было. Ожидаемые аргументы (файлы, подлежащие обработке) доступны в поле files объекта argparse.Namespace. Этот объект заполняется в строке 17 путем вызова parse_args().
Чтобы посмотреть на полный сценарий с изменениями, описанными выше, разверните блок кода ниже:
# sha1sum_argparse.py
import argparse
import hashlib
import pathlib
import sys
def process_file(filename: str) -> bytes:
return pathlib.Path(filename).read_bytes()
def process_stdin() -> bytes:
return bytes("".join(sys.stdin), "utf-8")
def sha1sum(data: bytes) -> str:
sha1_hash = hashlib.sha1()
sha1_hash.update(data)
return sha1_hash.hexdigest()
def output_sha1sum(data: bytes, filename: str = "-") -> None:
print(f"{sha1sum(data)} {filename}")
def init_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage="%(prog)s [OPTION] [FILE]...",
description="Print or check SHA1 (160-bit) checksums.",
)
parser.add_argument(
"-v", "--version", action="version",
version=f"{parser.prog} version 1.0.0"
)
parser.add_argument("files", nargs="*")
return parser
def main() -> None:
parser = init_argparse()
args = parser.parse_args()
if not args.files:
output_sha1sum(process_stdin())
for file in args.files:
if file == "-":
output_sha1sum(process_stdin(), "-")
continue
try:
output_sha1sum(process_file(file), file)
except (FileNotFoundError, IsADirectoryError) as err:
print(f"{parser.prog}: {file}: {err.strerror}", file=sys.stderr)
if __name__ == "__main__":
main()
Чтобы проиллюстрировать непосредственную выгоду, которую вы получите, введя argparse в эту программу, выполните следующие действия:
$ python sha1sum_argparse.py --help
usage: sha1sum_argparse.py [OPTION] [FILE]...
Print or check SHA1 (160-bit) checksums.
positional arguments:
files
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
Чтобы подробнее ознакомиться с argparse, ознакомьтесь с Создание интерфейсов командной строки с помощью Python argparse.
getopt
getopt находит свои истоки в getopt C-функции. Это облегчает синтаксический анализ командной строки и параметров обработки, аргументы параметров и аргументов. Перейдите к parse из seq_parse.py, чтобы использовать getopt:
def parse():
options, arguments = getopt.getopt(
sys.argv[1:], # Arguments
'vhs:', # Short option definitions
["version", "help", "separator="]) # Long option definitions
separator = "\n"
for o, a in options:
if o in ("-v", "--version"):
print(VERSION)
sys.exit()
if o in ("-h", "--help"):
print(USAGE)
sys.exit()
if o in ("-s", "--separator"):
separator = a
if not arguments or len(arguments) > 3:
raise SystemExit(USAGE)
try:
operands = [int(arg) for arg in arguments]
except ValueError:
raise SystemExit(USAGE)
return separator, operands
getopt.getopt() принимает следующие аргументы:
- Обычный список аргументов за вычетом имени скрипта,
sys.argv[1:] - Строка, определяющая короткие параметры
- Список строк для длинных опций
Обратите внимание, что короткий параметр, за которым следует двоеточие (:), содержит аргумент option, а длинный параметр, за которым следует знак равенства (=), содержит аргумент option.
Оставшийся код seq_getopt.py такой же, как и seq_parse.py, и доступен в свернутом блоке кода ниже:
# seq_getopt.py
from typing import List, Tuple
import getopt
import sys
USAGE = f"Usage: python {sys.argv[0]} [--help] | [-s <sep>] [first [incr]] last"
VERSION = f"{sys.argv[0]} version 1.0.0"
def seq(operands: List[int], sep: str = "\n") -> str:
first, increment, last = 1, 1, 1
if len(operands) == 1:
last = operands[0]
elif len(operands) == 2:
first, last = operands
if first > last:
increment = -1
elif len(operands) == 3:
first, increment, last = operands
last = last - 1 if first > last else last + 1
return sep.join(str(i) for i in range(first, last, increment))
def parse(args: List[str]) -> Tuple[str, List[int]]:
options, arguments = getopt.getopt(
args, # Arguments
'vhs:', # Short option definitions
["version", "help", "separator="]) # Long option definitions
separator = "\n"
for o, a in options:
if o in ("-v", "--version"):
print(VERSION)
sys.exit()
if o in ("-h", "--help"):
print(USAGE)
sys.exit()
if o in ("-s", "--separator"):
separator = a
if not arguments or len(arguments) > 3:
raise SystemExit(USAGE)
try:
operands = [int(arg) for arg in arguments]
except:
raise SystemExit(USAGE)
return separator, operands
def main() -> None:
args = sys.argv[1:]
if not args:
raise SystemExit(USAGE)
sep, operands = parse(args)
print(seq(operands, sep))
if __name__ == "__main__":
main()
Далее вы ознакомитесь с некоторыми внешними пакетами, которые помогут вам анализировать аргументы командной строки Python.
Несколько внешних пакетов Python
Основываясь на существующих соглашениях, которые вы видели в этом руководстве, в Python Package Index (PyPI) доступно несколько библиотек, которые выполняют гораздо больше шагов для облегчения реализации и обслуживания командной строки.линейные интерфейсы.
В следующих разделах предлагается ознакомиться с Click и Python Prompt Toolkit. Вы познакомитесь лишь с очень ограниченными возможностями этих пакетов, поскольку для того, чтобы оценить их по достоинству, потребуется полное руководство, если не целая серия!
Нажмите
На момент написания этой статьи, Click является, пожалуй, самой продвинутой библиотекой для создания сложного интерфейса командной строки для программы на Python. Он используется в нескольких продуктах Python, в первую очередь в Flask и Black. Прежде чем вы попробуете выполнить следующий пример, вам необходимо установить Click либо в виртуальной среде Python, либо в вашей локальной среде. Если вы не знакомы с концепцией виртуальных сред, ознакомьтесь с Виртуальные среды Python: учебник для начинающих.
Чтобы установить Click, выполните следующие действия:
$ python -m pip install click
Итак, как Click может помочь вам обрабатывать аргументы командной строки Python? Вот вариант seq программы, использующей Click:
# seq_click.py
import click
@click.command(context_settings=dict(ignore_unknown_options=True))
@click.option("--separator", "-s",
default="\n",
help="Text used to separate numbers (default: \\n)")
@click.version_option(version="1.0.0")
@click.argument("operands", type=click.INT, nargs=-1)
def seq(operands, separator) -> str:
first, increment, last = 1, 1, 1
if len(operands) == 1:
last = operands[0]
elif len(operands) == 2:
first, last = operands
if first > last:
increment = -1
elif len(operands) == 3:
first, increment, last = operands
else:
raise click.BadParameter("Invalid number of arguments")
last = last - 1 if first > last else last + 1
print(separator.join(str(i) for i in range(first, last, increment)))
if __name__ == "__main__":
seq()
Установка значения ignore_unknown_options равным True гарантирует, что Click не будет обрабатывать отрицательные аргументы в качестве параметров. Отрицательные целые числа являются допустимыми аргументами seq.
Как вы, наверное, заметили, вы многое получаете бесплатно! Нескольких хорошо продуманных декораторов достаточно, чтобы скрыть шаблонный код, позволяя вам сосредоточиться на основном коде, который является содержимым seq() в этом примере.
Примечание: Подробнее о декораторах Python читайте в Руководстве по декораторам Python.
Остается только импортировать click. Декларативный подход к оформлению основной команды, seq(), устраняет повторяющийся код, который в противном случае был бы необходим. Это может быть любое из следующих действий:
- Определение справки или процедуры использования
- Обработка версии программы
- Запись и настройка значений по умолчанию для параметров
- Проверка аргументов, включая тип
Новая реализация seq только начинается. Click предлагает множество дополнительных функций, которые помогут вам создать профессиональный интерфейс командной строки:
- Раскраска вывода
- Запрос на ввод пропущенных аргументов
- Команды и подкоманды
- Проверка типа аргумента
- Обратный вызов параметров и аргументов
- Проверка пути к файлу
- Индикатор выполнения
Также есть много других функций. Ознакомьтесь с Написанием инструментов командной строки на Python с помощью Click, чтобы увидеть более конкретные примеры, основанные на Click.
Набор инструментов Python Prompt Toolkit
Существуют и другие популярные пакеты Python, которые решают проблему интерфейса командной строки, например, docopt для Python. Таким образом, вам может показаться несколько нелогичным выбор набора подсказок.
Набор инструментов Python Prompt Toolkit предоставляет функции, которые могут заставить ваше приложение командной строки отойти от философии Unix. Однако это помогает преодолеть разрыв между загадочным интерфейсом командной строки и полноценным графическим интерфейсом пользователя. Другими словами, это может помочь сделать ваши инструменты и программы более удобными для пользователя.
Вы можете использовать этот инструмент в дополнение к обработке аргументов командной строки Python, как в предыдущих примерах, но это дает вам путь к подходу, подобному пользовательскому интерфейсу, без необходимости полагаться на полный Набор инструментов пользовательского интерфейса Python. Чтобы использовать prompt_toolkit, вам необходимо установить его с помощью pip:
$ python -m pip install prompt_toolkit
Возможно, следующий пример покажется вам немного надуманным, но его цель состоит в том, чтобы подтолкнуть вас к новым идеям и немного отвлечь от более строгих аспектов командной строки в отношении соглашений, которые вы видели в этом руководстве.
Поскольку вы уже ознакомились с основной логикой этого примера, в приведенном ниже фрагменте кода представлен только тот код, который значительно отличается от предыдущих примеров:
def error_dlg():
message_dialog(
title="Error",
text="Ensure that you enter a number",
).run()
def seq_dlg():
labels = ["FIRST", "INCREMENT", "LAST"]
operands = []
while True:
n = input_dialog(
title="Sequence",
text=f"Enter argument {labels[len(operands)]}:",
).run()
if n is None:
break
if n.isdigit():
operands.append(int(n))
else:
error_dlg()
if len(operands) == 3:
break
if operands:
seq(operands)
else:
print("Bye")
actions = {"SEQUENCE": seq_dlg, "HELP": help, "VERSION": version}
def main():
result = button_dialog(
title="Sequence",
text="Select an action:",
buttons=[
("Sequence", "SEQUENCE"),
("Help", "HELP"),
("Version", "VERSION"),
],
).run()
actions.get(result, lambda: print("Unexpected action"))()
Приведенный выше код содержит способы взаимодействия и, возможно, указания пользователям по вводу ожидаемых входных данных, а также по интерактивной проверке вводимых данных с помощью трех диалоговых окон:
button_dialogmessage_dialoginput_dialog
Инструментарий Python Prompt предоставляет множество других функций, предназначенных для улучшения взаимодействия с пользователями. Вызов обработчика в main() запускается вызовом функции, хранящейся в словаре. Ознакомьтесь с Эмуляцией операторов switch/case в Python, если вы никогда раньше не сталкивались с этой идиомой Python.
Вы можете увидеть полный пример работы программы с использованием prompt_toolkit, развернув блок кода ниже:
# seq_prompt.py
import sys
from typing import List
from prompt_toolkit.shortcuts import button_dialog, input_dialog, message_dialog
def version():
print("Version 1.0.0")
def help():
print("Print numbers from FIRST to LAST, in steps of INCREMENT.")
def seq(operands: List[int], sep: str = "\n"):
first, increment, last = 1, 1, 1
if len(operands) == 1:
last = operands[0]
elif len(operands) == 2:
first, last = operands
if first > last:
increment = -1
elif len(operands) == 3:
first, increment, last = operands
last = last - 1 if first > last else last + 1
print(sep.join(str(i) for i in range(first, last, increment)))
def error_dlg():
message_dialog(
title="Error",
text="Ensure that you enter a number",
).run()
def seq_dlg():
labels = ["FIRST", "INCREMENT", "LAST"]
operands = []
while True:
n = input_dialog(
title="Sequence",
text=f"Enter argument {labels[len(operands)]}:",
).run()
if n is None:
break
if n.isdigit():
operands.append(int(n))
else:
error_dlg()
if len(operands) == 3:
break
if operands:
seq(operands)
else:
print("Bye")
actions = {"SEQUENCE": seq_dlg, "HELP": help, "VERSION": version}
def main():
result = button_dialog(
title="Sequence",
text="Select an action:",
buttons=[
("Sequence", "SEQUENCE"),
("Help", "HELP"),
("Version", "VERSION"),
],
).run()
actions.get(result, lambda: print("Unexpected action"))()
if __name__ == "__main__":
main()
Когда вы выполните приведенный выше код, перед вами откроется диалоговое окно с предложением к действию. Затем, если вы выберете действие Последовательность, отобразится другое диалоговое окно. После сбора всех необходимых данных, параметров или аргументов диалоговое окно исчезает, а результат выводится в командной строке, как в предыдущих примерах:
По мере развития командной строки и появления попыток более творчески взаимодействовать с пользователями, другие пакеты, такие как PyInquirer, также позволяют использовать интерактивный подход.
Для дальнейшего изучения мира текстового пользовательского интерфейса (TUI) ознакомьтесь с Создание пользовательских интерфейсов консоли и Раздел сторонних разработчиков в Вашем руководстве по функции печати на Python.
Если вы заинтересованы в изучении решений, основанных исключительно на графическом интерфейсе пользователя, то вы можете ознакомиться со следующими ресурсами:
- Как создать графическое приложение на Python с помощью wxPython
- Python и PyQt: Создание настольного калькулятора с графическим интерфейсом
- Создайте мобильное приложение на платформе Kivy Python
Заключение
В этом руководстве вы ознакомились со многими различными аспектами работы с аргументами командной строки Python. Вы должны быть готовы применить следующие навыки к своему коду:
- Соглашения и псевдостандарты Аргументов командной строки Python
- Происхождение из
sys.argvв Python - Использование из
sys.argvдля обеспечения гибкости при запуске ваших программ на Python - Стандартные библиотеки Python, такие как
argparseилиgetopt, которые обрабатывают абстрактную командную строку - Мощные пакеты Python, такие как
clickиpython_toolkit, еще больше улучшат удобство использования ваших программ
Независимо от того, запускаете ли вы небольшой скрипт или сложное текстовое приложение, при использовании интерфейса командной строки вы значительно улучшите удобство работы с вашим программным обеспечением на Python. На самом деле, вы, вероятно, один из таких пользователей!
При следующем использовании приложения вы по достоинству оцените документацию, которую вы предоставили вместе с параметром --help, или тот факт, что вы можете передавать параметры и аргументы вместо изменения исходного кода для предоставления других данных.
Дополнительные ресурсы
Чтобы получить более подробную информацию об аргументах командной строки Python и их многочисленных аспектах, вы можете ознакомиться со следующими ресурсами:
- Сравнение библиотек синтаксического анализа командной строки Python – Argparse, Docopt и Click
- Python, Ruby и Golang: сравнение приложений командной строки

