4 votes

Construisez une liste de listes à partir de la ligne de commande

Je suis en train d'écrire une petite simulation en python, qui est censée agréger les résultats de différentes manières en fonction des arguments de la ligne de commande.

Une fois que la simulation a été exécutée et que l'objet Simulation contient les résultats bruts, je veux utiliser la méthode Simulation.sample(list_of_objects) ou la méthode Simulation.sample_differently() pour générer des sorties pour chaque échantillonneur spécifié. list_of_objects devrait être soit un range(N) soit une liste spécifiée explicitement en ligne de commande.

Par exemple, j'aimerais que les calculs suivants se produisent.

$ simulation --sample 5
[Simulation.sample(range(5))]
$ simulation --sample-objects 0 1 2 3 a
[Simulation.sample([0, 1, 2, 3, "a"])]
$ simulation --sample 4 --sample-objects 1 3 "b"
[Simulation.sample(range(4)), Simulation.sample([1, 3, "b"])]
$ simulation --sample-differently --sample-objects 1
[Simulation.sample_differently(), Simulation.sample([1])]
$ simulation --sample-objects 0 1 2 --sample-objects 3 4
[Simulation.sample([0, 1, 2]), Simulation.sample([3, 4])]

J'ai pensé le faire de la manière suivante.

def parse_objects_to_sampler(object_strings):
    objects = []
    for entry in object_strings:
        try:
            objects.append(int(entry))
        except ValueError:
            objects.append(entry)
    return lambda simulation: simulation.sample(objects))

parser = argparse.ArgumentParser()
parser.add_argument(
    "--sample", action=append,
    type=lambda x: lambda simulation: simulation.sample(range(int(x))))
parser.add_argument(
    "--sample-differently", action="append_const", dest="sample",
    const=Simulation.sample_differently)
parser.add_argument(
    "--sample-objects", nargs="*", action="append", dest="sample",
    type=parse_objects_to_sampler)

for sampler in parser.parse().sample:
    sampler(Simulation)

Malheureusement, le constructeur type opère sur chaque argument de ligne de commande individuel, et non sur la liste de plusieurs d'entre eux générée pour nargs≠None, donc l'approche ci-dessus ne fonctionne pas.

Quelle est la meilleure façon, selon les principes de python, d'obtenir le comportement décrit ci-dessus ?

1voto

Qichao Zhao Points 171

Je pense que quelqu'un avec plus d'expérience que moi pourrait parler du "plus pythonique", mais ma façon d'aborder cela serait d'accepter une chaîne CSV et ensuite d'utiliser la fonction parse_objects_to_sampler pour découper la chaîne et effectuer d'autres logiques.

Donc:

def parse_objects_to_sampler(input_string):
object_string = input_string.split(",")

objects = []
for entry in object_strings:
    try:
        objects.append(int(entry))
    except ValueError:
        objects.append(entry)
return lambda simulation: simulation.sample(objects))

Ensuite, vous appelleriez, par exemple:

simulation --sample-objects "0,1,2,3,a"

En espérant que cela vous donnera le résultat que vous voulez!

1voto

hpaulj Points 6132

type devrait se concentrer sur le test des entrées et leur conversion en entrées de base. Il accepte une chaîne en entrée et renvoie un objet, ou déclenche une erreur si la chaîne est invalide. Pour traiter les éléments d'une liste nargs* comme un ensemble, vous devez les traiter ultérieurement, après l'analyse.

La manière pythonique (ou en général une bonne programmation) est de diviser la tâche en plusieurs parties. Utilisez argparse pour seulement analyser les entrées, et le code suivant pour construire enfin la liste finale d'objets simulation.

Par exemple, je pense que ce parseur acceptera toutes vos entrées (je ne l'ai pas testé) :

parser = argparse.ArgumentParser()
parser.add_argument("--sample", type=int, action='append')
parser.add_argument("--sample-differently", action="store_true")
parser.add_argument("--sample-objects", nargs="*", action="append")
args = parser.parse_args()

L'objectif est d'accepter un entier avec --sample, une liste de chaînes avec --sample-objects et une valeur Vrai/Faux avec --sample-differently.

Ensuite, je peux construire la liste des plages et des objets simulation à partir de ces arguments (encore une fois, non testé) :

alist = []
if args.sample_differently:
    alist.append(Simulation.sample_differently())
for i in args.sample:
    # est un nombre
    alist.append(Simulation.sample(range(i)))
for i in args.sample_objects:
    # i sera une liste de chaînes
    def foo(i):
        # convertir conditionnellement les chaînes en entiers
        res = []
        for j in i:
            try:
                j = int(j)
            except ValueError:
                pass
        res.append(j)
        return res
    alist.append(Simulation.sample(foo(i)))

Si j'ai bien fait les choses, alist devrait correspondre à vos listes souhaitées.

Vous pourriez créer une classe d'action personnalisée qui effectuerait ce type d'ajout avec Simulation.sample. L'action obtient la liste entière en tant que values, qu'elle peut traiter et ajouter à l'espace de noms. Mais cela ne fait pas gagner de codage par rapport à ce que j'ai décrit.

\===============

Ce duo de définitions pourrait corriger votre argument '--samples-objects' :

def intorstr(astr):
        # convertir conditionnellement les chaînes en entiers
        try:
            astr = int(astr)
        except ValueError:
            pass
        return astr

class SamplesAction(argparse._AppendAction):
    # adapté de _AppendAction
    def __call__(self, parser, namespace, values, option_string=None):
         values = Simulation.sample(values)
         items = _copy.copy(_ensure_value(namespace, self.dest, []))
         items.append(values)
         setattr(namespace, self.dest, items)

parser.add_argument("--sample-objects", nargs="*", 
    action=SamplesAction, dest="sample", type=intorstr)

J'ignore les raisons pour lesquelles vous utilisez lambda simulation: simulation..... Je pense que ce serait moins confus si vous réécriviez cela comme une définition de fonction ou de classe. Trop de lambdas obscurcissent le code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X