web-dev-qa-db-fra.com

Modifier un dict Python en le parcourant

Disons que nous avons un dictionnaire Python d, et nous le parcourons comme ceci:

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 quelques transformations de la boîte noire.)

En d'autres termes, nous essayons d'ajouter/supprimer des éléments à d tout en effectuant une itération dessus à l'aide de iteritems.

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

(Il est assez évident de résoudre ce problème s'il est cassé, ce n'est donc pas l'angle que je recherche.)

66
NPE

Il est explicitement mentionné sur la page de documentation Python (pour Python 2.7 ) que

L'utilisation de iteritems() lors de l'ajout ou de la suppression d'entrées dans le dictionnaire peut générer une RuntimeError ou ne pas pouvoir itérer sur toutes les entrées.

De même pour Python 3 .

Il en va de même pour iter(d), d.iterkeys() et d.itervalues(), et j'irai jusqu'à dire que c'est le cas pour for k, v in d.items(): (je ne me souviens pas exactement de ce que for fait, mais je ne serais pas surpris que l'implémentation appelle iter(d)).

44

Alex Martelli se penche là-dessus ici .

Il peut ne pas être sûr de changer de conteneur (par exemple, dict) en boucle sur le conteneur . Donc, del d[f(k)] peut ne pas être sûr. Comme vous le savez, la solution consiste à utiliser d.items() (pour effectuer une boucle sur une copie indépendante du conteneur) au lieu de d.iteritems() (qui utilise le même conteneur sous-jacent).

Vous pouvez modifier la valeur à un index existant du dict, mais insérer des valeurs dans de nouveaux index (par exemple, d[g(k)]=v) risque de ne pas fonctionner.

43
unutbu

Vous ne pouvez pas faire cela, au moins avec d.iteritems(). Je l'ai essayé, et Python échoue avec

RuntimeError: dictionary changed size during iteration

Si vous utilisez plutôt d.items(), alors cela fonctionne.

En Python 3, d.items() est une vue dans le dictionnaire, comme d.iteritems() en Python 2. Pour cela, utilisez plutôt d.copy().items(). Cela nous permettra également de parcourir une copie du dictionnaire afin d’éviter de modifier la structure de données sur laquelle nous itérons.

20
murgatroid99

Le code suivant montre que ceci n'est pas bien défini:

def f(x):
    return x

def g(x):
    return x+1

def h(x):
    return x+10

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[g(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[h(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

Le premier exemple appelle g (k) et lève une exception (la taille du dictionnaire a été modifiée au cours de l'itération).

Le deuxième exemple appelle h(k) et ne lève aucune exception, mais affiche:

{21: 'axx', 22: 'bxx', 23: 'cxx'}

Ce qui, en regardant le code, semble faux - j'aurais espéré quelque chose comme:

{11: 'ax', 12: 'bx', 13: 'cx'}
6
combatdave

J'ai un grand dictionnaire contenant des tableaux Numpy, donc la chose dict.copy (). Keys () suggérée par @ murgatroid99 n'était pas faisable (bien que cela ait fonctionné). Au lieu de cela, je viens de convertir la keys_view en une liste et cela a bien fonctionné (en Python 3.4):

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

Je réalise que cela ne plonge pas dans le domaine philosophique du fonctionnement interne de Python comme les réponses ci-dessus, mais il fournit une solution pratique au problème énoncé.

4
2cynykyl

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

Python List peut être itéré même si vous le modifiez pendant l'itération . Ainsi, pour le code suivant, il imprimera 1 infiniment.

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

Donc, en utilisant list et dict en collaboration, vous pouvez 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))
1
Zeel Shah

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

J'ai fini par construire la routine suivante, qui peut aussi être trouvée 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 pourrait être utilisée à la place de d.iteritems() ci-dessus pour avoir l'effet souhaité.

0
Jason R. Coombs

Python 3, vous devriez juste:

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

ou utiliser:

t2 = t1.copy()

Vous ne devriez jamais modifier le dictionnaire d'origine, cela créerait de la confusion, ainsi que des bugs potentiels ou RunTimeErrors. Sauf si vous venez d’ajouter au dictionnaire de nouveaux noms de clés.

0
Dexter