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:

 $ ./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'}

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

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

Ответы

Ответ 1

Отключить это: 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'}

Ответ 2

Для полноты и аналогично 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

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

>>> 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}

Ответ 3

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

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

Ответ 4

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

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

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

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 'd.

Тем не менее, очень непросто, чтобы пользователь напечатал что-то, правильно процитированное, которое выглядит как словарь 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'}

Ответ 5

Один из самых простых способов, который я нашел, - это проанализировать словарь как список, а затем преобразовать его в словарь. Например, используя 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'})

Ответ 6

Общий совет: НЕ ИСПОЛЬЗУЙТЕ 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'})

Ответ 7

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

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

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

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

Ответ 8

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

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

Скажем, следующее называется 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)