Comment passer une liste en argument de ligne de commande avec argparse?

443

J'essaye de passer une liste comme argument à un programme de ligne de commande. Existe-t-il une argparseoption pour passer une liste en option?

parser.add_argument('-l', '--list',
                      type=list, action='store',
                      dest='list',
                      help='<Required> Set flag',
                      required=True)

Le script est appelé comme ci-dessous

python test.py -l "265340 268738 270774 270817"
user2125827
la source

Réponses:

883

TL; DR

Utilisez l' nargsoption ou le 'append'paramètre de l' actionoption (selon la façon dont vous souhaitez que l'interface utilisateur se comporte).

nargs

parser.add_argument('-l','--list', nargs='+', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 2345 3456 4567

nargs='+'prend 1 ou plusieurs arguments, nargs='*'zéro ou plus.

ajouter

parser.add_argument('-l','--list', action='append', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 -l 2345 -l 3456 -l 4567

Avec appendvous fournissez l'option plusieurs fois pour constituer la liste.

Ne pas utiliser type=list!!! - Il n'y a probablement pas de situation où vous voulez utiliser type=listavec argparse. Déjà.


Jetons un coup d'œil plus en détail sur certaines des différentes façons dont on pourrait essayer de le faire, et le résultat final.

import argparse

parser = argparse.ArgumentParser()

# By default it will fail with multiple arguments.
parser.add_argument('--default')

# Telling the type to be a list will also fail for multiple arguments,
# but give incorrect results for a single argument.
parser.add_argument('--list-type', type=list)

# This will allow you to provide multiple arguments, but you will get
# a list of lists which is not desired.
parser.add_argument('--list-type-nargs', type=list, nargs='+')

# This is the correct way to handle accepting multiple arguments.
# '+' == 1 or more.
# '*' == 0 or more.
# '?' == 0 or 1.
# An int is an explicit number of arguments to accept.
parser.add_argument('--nargs', nargs='+')

# To make the input integers
parser.add_argument('--nargs-int-type', nargs='+', type=int)

# An alternate way to accept multiple inputs, but you must
# provide the flag once per input. Of course, you can use
# type=int here if you want.
parser.add_argument('--append-action', action='append')

# To show the results of the given option to screen.
for _, value in parser.parse_args()._get_kwargs():
    if value is not None:
        print(value)

Voici la sortie à laquelle vous pouvez vous attendre:

$ python arg.py --default 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ python arg.py --list-type 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ # Quotes won't help here... 
$ python arg.py --list-type "1234 2345 3456 4567"
['1', '2', '3', '4', ' ', '2', '3', '4', '5', ' ', '3', '4', '5', '6', ' ', '4', '5', '6', '7']

$ python arg.py --list-type-nargs 1234 2345 3456 4567
[['1', '2', '3', '4'], ['2', '3', '4', '5'], ['3', '4', '5', '6'], ['4', '5', '6', '7']]

$ python arg.py --nargs 1234 2345 3456 4567
['1234', '2345', '3456', '4567']

$ python arg.py --nargs-int-type 1234 2345 3456 4567
[1234, 2345, 3456, 4567]

$ # Negative numbers are handled perfectly fine out of the box.
$ python arg.py --nargs-int-type -1234 2345 -3456 4567
[-1234, 2345, -3456, 4567]

$ python arg.py --append-action 1234 --append-action 2345 --append-action 3456 --append-action 4567
['1234', '2345', '3456', '4567']

Points à retenir :

  • Utilisez nargsouaction='append'
    • nargspeut être plus simple du point de vue de l'utilisateur, mais cela peut ne pas être intuitif s'il y a des arguments positionnels parce argparsequ'il ne peut pas dire ce qui devrait être un argument positionnel et ce qui appartient au nargs; si vous avez des arguments positionnels, cela action='append'peut être un meilleur choix.
    • Ce qui précède est vrai que si nargsest donné '*', '+'ou '?'. Si vous fournissez un nombre entier (tel que 4), il n'y aura aucun problème à mélanger les options avec nargset les arguments positionnels car argparseils sauront exactement combien de valeurs attendre pour l'option.
  • N'utilisez pas de guillemets sur la ligne de commande 1
  • Ne pas utiliser type=list, car il renverra une liste de listes
    • Cela se produit parce que sous le capot argparseutilise la valeur de typepour contraindre chaque argument donné individuel que vous avez choisi type, et non l'agrégat de tous les arguments.
    • Vous pouvez utiliser type=int(ou autre) pour obtenir une liste des entiers (ou autre)

1 : Je ne veux pas dire en général .. Je veux dire utiliser des guillemets pour passer une listeargparse n'est pas ce que vous voulez.

SethMMorton
la source
3
Et une liste de chaînes? Cela transforme plusieurs arguments de chaîne ("wassup", "quelque chose" et "else") en une liste de listes qui ressemble à ceci: [['w', 'a', 's', 's', 'u' , 'p'], ['s', 'o', 'm', 'e', ​​'t', 'h', 'i', 'n', 'g'], ['e', ' l ',' s ',' e ']]
rd108
3
@ rd108 Je vois, je parie que vous utilisez l' type=listoption. Ne l'utilisez pas. Cela transforme une chaîne en liste, et donc les listes de listes.
SethMMorton
1
@Dror Toutes les entrées sont supposées être des chaînes, sauf si vous définissez le typeparamètre sur un autre objet. Par défaut, cette méthode renvoie une liste de chaînes.
SethMMorton
1
--pourrait diviser les options par rapport aux arguments positionnels. prog --opt1 par1 ... -- posp1 posp2 ...
0andriy
1
cela peut ne pas être intuitif s'il y a des arguments positionnels car argparse ne peut pas dire ce qui devrait être un argument positionnel et ce qui appartient aux nargs . --aide à comprendre cela comme le montre l'exemple dans mon commentaire précédent. Fournitures utilisateur IOW --suivies de tous les arguments positionnels.
0andriy
83

Je préfère passer une chaîne délimitée que j'analyserai plus tard dans le script. Les raisons en sont: la liste peut être de n'importe quel type intou str, et parfois en utilisant nargsje rencontre des problèmes s'il y a plusieurs arguments optionnels et arguments positionnels.

parser = ArgumentParser()
parser.add_argument('-l', '--list', help='delimited list input', type=str)
args = parser.parse_args()
my_list = [int(item) for item in args.list.split(',')]

Alors,

python test.py -l "265340,268738,270774,270817" [other arguments]

ou,

python test.py -l 265340,268738,270774,270817 [other arguments]

fonctionnera bien. Le délimiteur peut également être un espace, qui imposerait des guillemets autour de la valeur de l'argument comme dans l'exemple de la question.

dojuba
la source
57
Vous pouvez définir l' typeargument sur au lambda s: [int(time) for item in s.split(',')]lieu du post-traitement args.list.
chepner
13
@ chepner, oui vous avez absolument raison et ce serait plus pythonique - juste une petite faute de frappe: int(time)devrait l'être int(item). Mon exemple était une version simplifiée de ce que je fais généralement, où je vérifie beaucoup d'autres choses plutôt qu'un simple traitement. Mais pour répondre simplement à la question, je trouve moi aussi votre chemin plus élégant ..
dojuba
1
cette réponse semble être la plus pythonique
Quetzalcoatl
1
Le commentaire de @chepner est une sérieuse compétence ninja +1
Briford Wylie
1
lambda items: list(csv.reader([items]))[0]avec la bibliothèque csv standard est une version modifiée du commentaire de @chepner pour quiconque s'inquiète de l'entrée CSV arbitraire (réf: réponse de @adamk ).
Kevin
19

De plus nargs, vous voudrez peut-être utiliser choicessi vous connaissez la liste à l'avance:

>>> parser = argparse.ArgumentParser(prog='game.py')
>>> parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
>>> parser.parse_args(['rock'])
Namespace(move='rock')
>>> parser.parse_args(['fire'])
usage: game.py [-h] {rock,paper,scissors}
game.py: error: argument move: invalid choice: 'fire' (choose from 'rock',
'paper', 'scissors')
Martin Thoma
la source
10

Utilisation du paramètre nargs dans la méthode add_argument d'Argparse

J'utilise nargs = ' ' comme paramètre add_argument. J'ai spécifiquement utilisé nargs = ' ' pour l'option de sélection des valeurs par défaut si je ne passe aucun argument explicite

Inclure un extrait de code comme exemple:

Exemple: temp_args1.py

Remarque: l' exemple de code ci-dessous est écrit en python3. En modifiant le format de l'instruction d'impression, peut s'exécuter en python2

#!/usr/local/bin/python3.6

from argparse import ArgumentParser

description = 'testing for passing multiple arguments and to get list of args'
parser = ArgumentParser(description=description)
parser.add_argument('-i', '--item', action='store', dest='alist',
                    type=str, nargs='*', default=['item1', 'item2', 'item3'],
                    help="Examples: -i item1 item2, -i item3")
opts = parser.parse_args()

print("List of items: {}".format(opts.alist))

Remarque: Je collecte plusieurs arguments de chaîne qui sont stockés dans la liste - opts.alist Si vous voulez une liste d'entiers, changez le paramètre de type sur parser.add_argument en int

Résultat d'exécution:

python3.6 temp_agrs1.py -i item5 item6 item7
List of items: ['item5', 'item6', 'item7']

python3.6 temp_agrs1.py -i item10
List of items: ['item10']

python3.6 temp_agrs1.py
List of items: ['item1', 'item2', 'item3']
Py_minion
la source
1
@Py_minion Existe-t-il un moyen d'utiliser une liste comme argument et d'avoir également la sortie en tant que liste? temp_args1.py -i [item5 ,item6, item7]et faire sortir la sortie sous forme de liste également (au lieu d'une liste imbriquée)
Moondra
@Moondra Oui. content que vous ayez demandé. `` `parser.add_argument ('- o', '--options', action = 'store', dest = 'opt_list', type = str, nargs = '*', default = sample_list, help =" Chaîne de bases de données séparés par un espace blanc. Exemples: \ -o option1 option2, -o option3 ")` `Ici 'sample_list' est de type liste avec les options par défaut. Ex: sample_list = [option4, option5]
Py_minion
1
@Py_minion Merci. Je vais le tester plus tard dans la journée.
Moondra
J'ai utilisé cela, c'est très utile pour passer la création de listes à partir des arguments.
siby
5

Si vous avez l'intention de faire en sorte qu'un seul commutateur prenne plusieurs paramètres, vous l'utilisez nargs='+'. Si votre exemple '-l' prend en fait des entiers:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    nargs='+',       # one or more parameters to this switch
    type=int,        # /parameters/ are ints
    dest='list',     # store in 'list'.
    default=[],      # since we're not specifying required.
)

print a.parse_args("-l 123 234 345 456".split(' '))
print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Produit

Namespace(list=[123, 234, 345, 456])
Namespace(list=[456])  # Attention!

Si vous spécifiez le même argument plusieurs fois, l'action par défaut ( 'store') remplace les données existantes.

L'alternative est d'utiliser l' appendaction:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    type=int,        # /parameters/ are ints
    dest='list',     # store in 'list'.
    default=[],      # since we're not specifying required.
    action='append', # add to the list instead of replacing it
)

print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Qui produit

Namespace(list=[123, 234, 345, 456])

Ou vous pouvez écrire un gestionnaire / action personnalisé pour analyser les valeurs séparées par des virgules afin que vous puissiez faire

-l 123,234,345 -l 456
kfsone
la source
5

Dans add_argument(), typeest juste un objet appelable qui reçoit une chaîne et renvoie une valeur d'option.

import ast

def arg_as_list(s):                                                            
    v = ast.literal_eval(s)                                                    
    if type(v) is not list:                                                    
        raise argparse.ArgumentTypeError("Argument \"%s\" is not a list" % (s))
    return v                                                                   


def foo():
    parser.add_argument("--list", type=arg_as_list, default=[],
                        help="List of values")

Cela permettra de:

$ ./tool --list "[1,2,3,4]"
wonder.mice
la source
Notez que si l'on devait passer des chaînes, cette méthode nécessiterait de les citer correctement sur la ligne de commande. Un utilisateur peut trouver cela inattendu. Si seulement l'analyse des entiers est correcte.
SethMMorton
1

Si vous avez une liste imbriquée où les listes internes ont différents types et longueurs et que vous souhaitez conserver le type, par exemple,

[[1, 2], ["foo", "bar"], [3.14, "baz", 20]]

alors vous pouvez utiliser la solution proposée par @ sam-mason à cette question , présentée ci-dessous:

from argparse import ArgumentParser
import json

parser = ArgumentParser()
parser.add_argument('-l', type=json.loads)
parser.parse_args(['-l', '[[1,2],["foo","bar"],[3.14,"baz",20]]'])

qui donne:

Namespace(l=[[1, 2], ['foo', 'bar'], [3.14, 'baz', 20]])
Meysam Sadeghi
la source
0

Je veux gérer le passage de plusieurs listes, valeurs entières et chaînes.

Lien utile => Comment passer une variable Bash à Python?

def main(args):
    my_args = []
    for arg in args:
        if arg.startswith("[") and arg.endswith("]"):
            arg = arg.replace("[", "").replace("]", "")
            my_args.append(arg.split(","))
        else:
            my_args.append(arg)

    print(my_args)


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

L'ordre n'est pas important. Si vous voulez passer une liste faire aussi entre "["et "]et séparer les utiliser une virgule.

Alors,

python test.py my_string 3 "[1,2]" "[3,4,5]"

Sortie => ['my_string', '3', ['1', '2'], ['3', '4', '5']], la my_argsvariable contient les arguments dans l'ordre.

alper
la source
0

Je pense que la solution la plus élégante est de passer une fonction lambda à "type", comme mentionné par Chepner. De plus, si vous ne savez pas à l'avance quel sera le délimiteur de votre liste, vous pouvez également passer plusieurs délimiteurs à re.split:

# python3 test.py -l "abc xyz, 123"

import re
import argparse

parser = argparse.ArgumentParser(description='Process a list.')
parser.add_argument('-l', '--list',
                    type=lambda s: re.split(' |, ', s),
                    required=True,
                    help='comma or space delimited list of characters')

args = parser.parse_args()
print(args.list)


# Output: ['abc', 'xyz', '123']
Nébulastique
la source
Voulez-vous dire -ldans l'exemple d'appel? D'où -nvenait-il?
Anthony
De plus, la solution ne fonctionne pas pour moi dans Python 3.8.2. Voici le code: parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s)). Voici l'entrée: script.py -l abc xyz, abc\nxyz. Enfin, voici le résultat:script.py: error: unrecognized arguments: xyz, abcnxyz
Anthony
Changer mon exemple pour que ça marche :)
Nebulastic