140 votes

Modifier un dicton Python tout en le parcourant

Supposons que nous ayons un dictionnaire Python d et nous itérons dessus comme suit :

for k, v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

( f et g ne sont que des transformations de boîte noire).

En d'autres termes, nous essayons d'ajouter/supprimer des éléments à d tout en le parcourant à l'aide de iteritems .

Est-ce bien défini ? Pourriez-vous fournir des références pour étayer votre réponse ?


Voir aussi <a href="https://stackoverflow.com/questions/11941817">Comment éviter l'erreur "RuntimeError : dictionary changed size during iteration" ? </a>pour la question distincte de savoir comment éviter le problème.

4voto

Dexter Points 1422

Python 3, vous devriez simplement :

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

ou de l'utiliser :

t2 = t1.copy()

Vous ne devez jamais modifier le dictionnaire d'origine, car cela entraîne des confusions ainsi que des bogues potentiels ou des RunTimeErrors. Sauf si vous ajoutez simplement de nouveaux noms de clés au dictionnaire.

2voto

questionto42 Points 1372

Cette question porte sur l'utilisation d'un itérateur (et il est assez amusant de constater que Python 2 .iteritems n'est plus supporté dans Python 3) pour supprimer ou ajouter des éléments, et il doit avoir un Non comme seule bonne réponse comme vous pouvez le trouver dans la réponse acceptée. Pourtant : la plupart des chercheurs essaient de trouver une solution, ils ne se soucient pas de la manière dont cela est fait techniquement, que ce soit un itérateur ou une récursion, et il n'y a pas d'autre moyen de trouver une solution. es une solution au problème :

Vous ne pouvez pas changer en boucle un dict sans utiliser une fonction supplémentaire (récursive).

Cette question doit donc être liée à une question dont la solution est opérationnelle :

Par les mêmes méthodes récursives, vous pourrez également ajouter des éléments comme le demande la question.


Puisque ma demande de lier cette question a été refusée, voici une copie de la solution qui permet de supprimer des éléments d'un dict. Voir Comment supprimer une paire clé/valeur à chaque fois que la clé choisie apparaît dans un dictionnaire profondément imbriqué ? (= "supprimer") pour les exemples / crédits / notes.

import copy

def find_remove(this_dict, target_key, bln_overwrite_dict=False):
    if not bln_overwrite_dict:
        this_dict = copy.deepcopy(this_dict)

    for key in this_dict:
        # if the current value is a dict, dive into it
        if isinstance(this_dict[key], dict):
            if target_key in this_dict[key]:
                this_dict[key].pop(target_key)

            this_dict[key] = find_remove(this_dict[key], target_key)

    return this_dict

dict_nested_new = find_remove(nested_dict, "sub_key2a")

L'astuce

L'astuce consiste à déterminer à l'avance si une clé cible fait partie des prochains enfants (= this_dict[key] = les valeurs de l'itération actuelle du dict) avant d'atteindre le niveau de l'enfant de manière récursive. Ce n'est qu'à ce moment-là que vous pourrez encore supprimer une paire clé/valeur du niveau enfant lors de l'itération d'un dictionnaire. Une fois que vous avez atteint le même niveau que la clé à supprimer et que vous essayez de la supprimer à partir de là, vous obtiendrez l'erreur :

RuntimeError: dictionary changed size during iteration

La solution récursive ne modifie que le sous-niveau des valeurs suivantes et évite donc l'erreur.

1voto

zps215 Points 51

J'ai rencontré le même problème et j'ai utilisé la procédure suivante pour le résoudre.

La liste Python peut être itérée même si vous la modifiez pendant l'itération. Ainsi, le code suivant imprimera des 1 à l'infini.

for i in list:
   list.append(1)
   print 1

L'utilisation de listes et de dictées en collaboration permet donc de résoudre ce problème.

d_list=[]
 d_dict = {} 
 for k in d_list:
    if d_dict[k] is not -1:
       d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted)
       d_dict[g(k)] = v # add a new item 
       d_list.append(g(k))

0voto

Jason R. Coombs Points 11130

Aujourd'hui, j'ai eu un cas d'utilisation similaire, mais au lieu de simplement matérialiser les clés du dictionnaire au début de la boucle, je voulais que les changements apportés au dict affectent l'itération du dict, qui était un dict ordonné.

J'ai fini par construire la routine suivante, qui peut également être trouvé dans jaraco.itertools :

def _mutable_iter(dict):
    """
    Iterate over items in the dict, yielding the first one, but allowing
    it to be mutated during the process.
    >>> d = dict(a=1)
    >>> it = _mutable_iter(d)
    >>> next(it)
    ('a', 1)
    >>> d
    {}
    >>> d.update(b=2)
    >>> list(it)
    [('b', 2)]
    """
    while dict:
        prev_key = next(iter(dict))
        yield prev_key, dict.pop(prev_key)

La docstring illustre l'utilisation. Cette fonction peut être utilisée à la place de d.iteritems() ci-dessus pour obtenir l'effet désiré.

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