2025, Nov 01 12:46
Как привязать флаги к отдельным файлам в argparse (Python)
Почему argparse в Python не связывает опции с файлами и как это обойти. Пошаговый разбор с intermixed parsing и пример предварительной обработки argv.
На первый взгляд создать CLI, который принимает несколько входных файлов с индивидуальными параметрами для каждого, кажется просто. Логично ожидать, что такой вызов сработает «из коробки»: флаг перед первым файлом относится только к нему, другой флаг перед вторым — только ко второму и так далее. Такой подход сделал популярным, например, ffmpeg:
python my.py -a file_a -b file_b --do-stuff=x file_cКак воспроизвести проблему с argparse
Ниже — минимальный пример, где мы задаём пару флагов и позиционный список входных файлов:
import argparse
import pathlib
cli = argparse.ArgumentParser()
cli.add_argument("-a", action="store_true")
cli.add_argument("-b", action="store_true")
cli.add_argument("--do-stuff", type=str)
cli.add_argument("files", type=pathlib.Path, nargs="+")
parsed = cli.parse_args()Запустите его, как показано выше, и получите:
error: unrecognized arguments: file_b file_cЗадумка этого синтаксиса — получить для каждого файла такую привязку опций:
file_a: {a: True}
file_b: {b: True}
file_c: {do_stuff: 'x'}Почему это не работает
Флаги вроде -a и -b — булевы: они не принимают значения. Поэтому file_a и file_b не интерпретируются как значения этих флагов, а считаются позиционными аргументами. ArgumentParser в Python не связывает опции с ближайшим позиционным аргументом и не учитывает порядок опций относительно позиционных параметров при формировании результата. Даже режим «intermixed parsing», описанный в документации (https://docs.python.org/3/library/argparse.html#intermixed-parsing), здесь не помогает. Вызовы parse_intermixed_args или parse_known_intermixed_args соберут все опции вне зависимости от их положения относительно file_a или любого другого позиционного входа. Иными словами, встроенного способа сказать «этот флаг относится только к следующему файлу» нет.
Рабочий подход с предварительной обработкой
Чтобы добиться нужного поведения, можно предварительно обработать sys.argv: разбить аргументы на срезы до каждого файла и разбирать каждый срез отдельно. Идея такая: сначала собрать список файлов, затем для каждого файла разобрать только те опции, которые стоят перед ним. Понадобится одна корректировка: читать имена файлов как str, чтобы потом найти их в исходной последовательности argv.
import sys
import argparse
import pathlib
cli = argparse.ArgumentParser()
cli.add_argument("-a", action="store_true")
cli.add_argument("-b", action="store_true")
cli.add_argument("--do-stuff", type=str)
# Читаем имена файлов как str, чтобы позже найти их в argv
cli.add_argument("files", type=str, nargs="+")
# Собираем список файлов, игнорируя положение опций
file_list = cli.parse_intermixed_args().files
per_input = {}
remaining = sys.argv[1:]
for fname in file_list:
# Находим текущий файл в оставшемся срезе argv
cut = remaining.index(fname) + 1
# Разбираем только аргументы для этого файла (всё до него включительно)
per_input[pathlib.Path(fname)] = cli.parse_args(remaining[:cut])
# Удаляем разобранный участок и продолжаем
del remaining[:cut]Так мы изолируем флаги, стоящие перед каждым файлом, и разбираем их так, будто программу запускали отдельно для каждого входа. Шаг с intermixed-разбором нужен лишь для получения списка файлов, не позволяя парсеру заранее «съесть» все опции.
Зачем это важно
Если вы хотите интерфейс командной строки в духе ffmpeg, где опции можно ограничивать отдельными входами, рассчитывать на то, что ArgumentParser учтёт контекст позиционных аргументов, — ошибка и источник странных результатов. Понимание того, что парсер не связывает опции с соседними позиционными параметрами, помогает избежать хрупких интерфейсов и вовремя понять, когда нужна предварительная обработка.
Выводы
ArgumentParser в Python не поддерживает привязку опций к файлам по порядку аргументов. Режим intermixed по духу близок, но всё равно собирает опции, не глядя на их расположение относительно файлов. Когда требуется настройка для каждого входа, разбивайте исходный argv сами, разбирайте каждый сегмент и явно формируйте соответствие «файл → опции». Так сохраняется привычный стиль команд и появляется прозрачная, привязанная к файлам карта параметров, которую вы и задумывали.