type=dict в argparse.add_argument()

Я пытаюсь настроить словарь как необязательный аргумент (используя argparse); следующая строка - это то, что у меня есть до сих пор:

parser.add_argument('-i','--image', type=dict, help='Generate an image map from the input file (syntax: {\'name\': <name>, \'voids\': \'#08080808\', \'0\': \'#00ff00ff\', \'100%%\': \'#ff00ff00\'}).')

Но запуск скрипта:

 $ ./script.py -i {'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}

script.py: error: argument -i/--image: invalid dict value: '{name:'

Хотя внутри интерпретатора

>>> a={'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}

работает просто отлично.

Итак, как мне передать аргумент вместо этого? Заранее спасибо.


person user975296    schedule 02.10.2011    source источник
comment
Вы можете читать такие форматы, как JSON, из внешнего файла или стандартного ввода, а затем анализировать его. Таким образом, ваш тип argparse будет на самом деле файлом.   -  person Yauhen Yakimovich    schedule 28.06.2013
comment
как сказал @wim в своем ответе, оболочка обрабатывает аргументы, прежде чем передавать их на python. Если вы добавите к своей команде «эхо» (echo ./script.py -i {'name': ...), вы увидите, что видит python (в основном он не получает никаких кавычек). В вашем случае, когда в вашем параметре нет $ (который может быть интерпретирован оболочкой как переменная среды), вы можете окружить свой dict двойными кавычками: ./script.py -i "{'name': 'img.png', ....}"   -  person Carlos Campderrós    schedule 27.08.2015


Ответы (12)


Necroting this: json.loads работает и здесь. Он не кажется слишком грязным.

import json
import argparse

test = '{"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"}'

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', type=json.loads)

args = parser.parse_args(['-i', test])

print(args.input)

Возвращает:

{u'0': u'#ff00ff00', u'100%': u'#f80654ff', u'voids': u'#00ff00ff', u'name': u'img.png'}

person Edd    schedule 01.08.2013
comment
json.loads — хороший выбор для type. Подобно int и float, он принимает строку и возвращает ValueError, если не может ее обработать. Это также безопаснее, чем eval. Для этой цели он может быть слишком общим (то есть он может обрабатывать список '[1, 2]'), но пользователь может справиться с этим после parse_args(). - person hpaulj; 02.08.2013
comment
Когда значения равны strings, int, float, все работает нормально. Для других типов значений, таких как bool, это не так (но передача 1 вместо True должна работать, но все хорошо написанный код). - person gerrit; 14.07.2017
comment
Вы можете добавить пример ввода cli? почти все, что я пробую, приводит к invalid loads value. например это работает. --input="{}" это не удается --input="{'foo': 'bar'}" - person J'e; 13.11.2020
comment
@J'e --input='{"foo" : "bar" }' будет работать, потому что синтаксис JSON требует двойных кавычек для строки. - person Ying Lyu; 26.04.2021

Для полноты, как и в случае с json.loads, вы можете использовать yaml.load (доступен в PyYAML в PyPI). Это имеет преимущество перед json в том, что нет необходимости заключать в кавычки отдельные ключи и значения в командной строке, если только вы не пытаетесь, скажем, принудительно преобразовать целые числа в строки или иным образом преодолеть семантику преобразования yaml. Но очевидно, что всю строку нужно будет заключить в кавычки, так как она содержит пробелы!

>>> import argparse
>>> import yaml
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-fna', '--filename-arguments', type=yaml.load)
>>> data = "{location: warehouse A, site: Gloucester Business Village}"
>>> ans = parser.parse_args(['-fna', data])
>>> print ans.filename_arguments['site']
Gloucester Business Village

Хотя, по общему признанию, в заданном вопросе многие ключи и значения должны были быть заключены в кавычки или перефразированы, чтобы предотвратить рвоту yaml. Использование следующих данных, кажется, работает довольно хорошо, если вам нужны числовые, а не строковые значения:

>>> parser.add_argument('-i', '--image', type=yaml.load)
>>> data = "{name: img.png, voids: 0x00ff00ff, '0%': 0xff00ff00, '100%': 0xf80654ff}"
>>> ans = parser.parse_args(['-i', data])
>>> print ans.image
{'100%': 4161164543L, 'voids': 16711935, 'name': 'img.png', '0%': 4278255360L}
person Hamish    schedule 10.12.2013
comment
Исправлены (отсутствуют) вызовы parser.parse_args. Спасибо, что указали на это, Хотшке. - person Hamish; 08.08.2014

Могу поспорить, что ваша оболочка возится с фигурными скобками, поскольку фигурные скобки — это синтаксис, используемый для функций расширения фигурных скобок во многих оболочках (см. здесь).

Передача сложного контейнера, такого как словарь, требующего от пользователя знания синтаксиса Python, кажется плохим выбором дизайна в интерфейсе командной строки. Вместо этого я бы рекомендовал просто передавать параметры один за другим в CLI внутри группа аргументов, а затем программно создайте словарь из проанализированной группы.

person wim    schedule 02.10.2011

Использование простого лямбда-анализа достаточно гибко:

parser.add_argument(
    '--fieldMap',
    type=lambda x: {k:int(v) for k,v in (i.split(':') for i in x.split(','))},
    help='comma-separated field:position pairs, e.g. Date:0,Amount:2,Payee:5,Memo:9'
)
person user1602    schedule 08.04.2020
comment
Это самое чистое решение на данный момент! Спасибо, очень помогло - person Rovshan Musayev; 09.10.2020
comment
Очень чистое решение, спасибо. Поскольку v используется в нескольких контекстах, это создает путаницу для меня, ниже также работает type=lambda e: {k:int(v) for k,v in (x.split(':') for x in e.split(','))}, - person BeingSachin; 28.07.2021

Сочетание фрагмента type= от @Edd и фрагмента ast.literal.eval от @Bradley дает наиболее прямое решение, ИМО. Он позволяет напрямую извлекать argval и даже принимает значение по умолчанию (в кавычках) для dict:

Фрагмент кода

parser.add_argument('--params', '--p', help='dict of params ', type=ast.literal.eval, default="{'name': 'adam'}")
args = parser.parse_args()

Запуск кода

python test.py --p "{'town': 'union'}"

обратите внимание на кавычки значения dict. Это цитирование работает в Windows и Linux (проверено с помощью [t]csh).

Получение Аргвала

dict=args.params
person frankeye    schedule 28.08.2020

Вы определенно можете ввести что-то похожее на литерал словаря в синтаксический анализатор аргументов, но вы должны заключить это в кавычки, поэтому, когда оболочка анализирует вашу командную строку, она появляется как

  • один аргумент вместо многих (символ пробела является обычным разделителем аргументов)
  • правильно цитируется (оболочка удаляет кавычки во время синтаксического анализа, потому что использует их для группировки)

Таким образом, что-то вроде этого может получить текст, который вы хотели, в вашу программу:

python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}"

Однако эта строка не является допустимым аргументом конструктора dict; вместо этого это правильный фрагмент кода Python. Вы можете сказать своему синтаксическому анализатору аргументов, что «тип» этого аргумента — eval, и это сработает:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-i','--image', type=eval, help='Generate an image map...')
args = parser.parse_args()
print args

и вызывая его:

% python MYSCRIPT.py -i "{\"name\": \"img.png\", \"voids\": \"#00ff00ff\",\"0\": \"#ff00ff00\",\"100%\": \"#f80654ff\"}"
Namespace(image={'0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png'})

Но это небезопасно; ввод может быть любым, и вы оцениваете произвольный код. Это было бы так же громоздко, но гораздо безопаснее было бы следующее:

import argparse
import ast

parser = argparse.ArgumentParser()
parser.add_argument('-i','--image', type=ast.literal_eval, help='Generate an image map...')
args = parser.parse_args()
print args

Это также работает, но ГОРАЗДО больше ограничивает то, что можно eval сделать.

Тем не менее, очень неудобно, чтобы пользователь печатал что-то, правильно заключенное в кавычки, похожее на словарь Python в командной строке. И вам нужно будет сделать некоторую проверку постфактум, чтобы убедиться, что они прошли в словаре, а не в чем-то другом, способном к оценке, и в нем были правильные ключи. Гораздо проще использовать, если:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--image-name", required=True)
parser.add_argument("--void-color", required=True)
parser.add_argument("--zero-color", required=True)
parser.add_argument("--full-color", required=True)

args = parser.parse_args()

image = {
    "name": args.image_name,
    "voids": args.void_color,
    "0%": args.zero_color,
    "100%": args.full_color
    }
print image

За:

% python MYSCRIPT.py --image-name img.png --void-color \#00ff00ff --zero-color \#ff00ff00 --full-color \#f80654ff
{'100%': '#f80654ff', 'voids': '#00ff00ff', 'name': 'img.png', '0%': '#ff00ff00'}
person Matt Anderson    schedule 02.10.2011
comment
Вау, спасибо за обзор возможностей; однако, несмотря на то, что в примере я указал только 0 и 100%, на самом деле это может быть любое значение (например, {'46%':'#0f0e0d0c','3629','#f0e0d0c0'}), что не предполагается в вашем последнем фрагменте кода... - person user975296; 04.10.2011

Один из самых простых способов, которые я нашел, - это проанализировать словарь как список, а затем преобразовать его в словарь. Например, используя Python3:

#!/usr/bin/env python3
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--image', type=str, nargs='+')
args = parser.parse_args()
if args.image is not None:
    i = iter(args.image)
    args.image = dict(zip(i, i))
print(args)

затем вы можете ввести в командной строке что-то вроде:

./script.py -i name img.png voids '#00ff00ff' 0 '#ff00ff00' '100%' '#f80654ff'

чтобы получить желаемый результат:

Namespace(image={'name': 'img.png', '0': '#ff00ff00', 'voids': '#00ff00ff', '100%': '#f80654ff'})
person Juan A. Navarro    schedule 05.12.2013

Общий совет: НЕ ИСПОЛЬЗУЙТЕ eval.

Если вам действительно нужно... "eval" опасен. Используйте его, если вы уверены, что никто не будет намеренно вводить вредоносные данные. Даже тогда могут быть недостатки. Я рассмотрел один плохой пример.

Однако использование eval вместо json.loads также имеет некоторые преимущества. Диктант на самом деле не обязательно должен быть действительным json. Следовательно, eval может быть довольно снисходительным в принятии "словарей". Мы можем позаботиться об «опасной» части, убедившись, что конечный результат действительно является словарем Python.

import json
import argparse

tests = [
  '{"name": "img.png","voids": "#00ff00ff","0": "#ff00ff00","100%": "#f80654ff"}',
  '{"a": 1}',
  "{'b':1}",
  "{'$abc': '$123'}",
  '{"a": "a" "b"}' # Bad dictionary but still accepted by eval
]
def eval_json(x):
  dicti = eval(x)
  assert isinstance(dicti, dict)
  return dicti

parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', type=eval_json)
for test in tests:
  args = parser.parse_args(['-i', test])
  print(args)

Выход:

Namespace(input={'name': 'img.png', '0': '#ff00ff00', '100%': '#f80654ff', 'voids': '#00ff00ff'})
Namespace(input={'a': 1})
Namespace(input={'b': 1})
Namespace(input={'$abc': '$123'})
Namespace(input={'a': 'ab'})
person Mayank Jaiswal    schedule 06.09.2016
comment
Это крайне опасный совет. Использование eval (очевидно) приведет к тому, что ввод из командной строки будет оцениваться как python. Это происходит до того, как он вернет значение, так что ваша проверка типов слишком запоздала. Кроме того, существует множество действительных опасных питонов, которые все равно вернут dict... Утверждение о том, что мы можем позаботиться об опасности, является неточным и опасным для распространения в качестве совета. - person Dave Rawks; 03.11.2016
comment
Хорошо. Соглашаться. Я меняю язык ответа. - person Mayank Jaiswal; 07.11.2016

Минимальный пример передачи аргументов в виде словаря из командной строки:

# file.py
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
                    required=False,
                    default=None,
                    type=json.loads
                )
args = parser.parse_args()
print(args.parameters)

и в терминале вы можете передать свои аргументы в виде словаря, используя строковый формат:

python file.py --parameters '{"a":1}'
person Galuoises    schedule 17.11.2020

Вы можете попробовать:

$ ./script.py -i "{'name': 'img.png','voids': '#00ff00ff','0': '#ff00ff00','100%': '#f80654ff'}"

Я не проверял это, на моем телефоне прямо сейчас.

Редактировать: Кстати, я согласен с @wim, я думаю, что каждый kv словаря в качестве аргумента был бы удобнее для пользователя.

person John Keyes    schedule 02.10.2011

Вот еще одно решение, так как мне пришлось сделать что-то подобное самому. Я использую модуль ast для преобразования словаря, который вводится в терминал в виде строки, в словарь. Это очень просто.

Фрагмент кода

Скажем, следующее называется test.py:

import argparse
import ast

parser = argparse.ArgumentParser()
parser.add_argument('--params', '--p', help='dict of params ',type=str)

options = parser.parse_args()

my_dict = options.params
my_dict = ast.literal_eval(my_dict)
print(my_dict)
for k in my_dict:
  print(type(my_dict[k]))
  print(k,my_dict[k])

Затем в строке терминала/cmd вы должны написать:

Запуск кода

python test.py --p '{"name": "Adam", "lr": 0.001, "betas": (0.9, 0.999)}'

Выход

{'name': 'Adam', 'lr': 0.001, 'betas': (0.9, 0.999)}
<class 'str'>
name Adam
<class 'float'>
lr 0.001
<class 'tuple'>
betas (0.9, 0.999)
person Bradley    schedule 05.09.2019

Решение TLDR. Ниже приведено самое простое и быстрое решение:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-par", "--parameters",
                    default={},
                    type=str)
args = parser.parse_args()

В функции parser.add_argument:

  1. Использовать объект словаря для объекта по умолчанию
  2. str как тип

Затем args.parameters будет автоматически преобразовано в словарь без необходимости использования ast.literal.eval или json.loads.

Мотивация: методы, опубликованные @Galuoises и @frankeye, похоже, не работают, когда default установлен как словарь в формате json, как показано ниже.

parser.add_argument("-par", "--parameters",
                required=False,  default="{\"k1\":v1, \"k2\":v2}",
                type=json.loads)

Это потому что

person Rilwan Adewoyin    schedule 19.12.2020