web-dev-qa-db-fra.com

Comment changer toutes les clés du dictionnaire dans une boucle for avec d.items ()?

Je voudrais de l'aide pour comprendre pourquoi ce code ne fonctionne pas comme prévu.

Si on veut changer la clé d'un dictionnaire mais garder la valeur, il/elle peut utiliser:

d[new_key] = d.pop[old_key]

Je veux modifier toutes les clés (et garder les valeurs en place) mais le code ci-dessous ignore certaines lignes - ("col2") reste inchangé. Est-ce parce que les dictionnaires ne sont pas ordonnés et que je continue à en changer les valeurs?

Comment pourrais-je changer les clés et conserver les valeurs sans créer un nouveau dictionnaire?

import time
import pprint

name_dict = {"col1": 973, "col2": "1452 29th Street",
             "col3": "Here is a value", "col4" : "Here is another value",
             "col5" : "NULL", "col6": "Scottsdale",
             "col7": "N/A", "col8" : "41.5946922",
             "col9": "Building", "col10" : "Commercial"}


for k, v in name_dict.items():
    print("This is the key: '%s' and this is the value '%s'\n" % (k, v) )
    new_key = input("Please enter a new key: ")
    name_dict[new_key] = name_dict.pop(k)
    time.sleep(4)

pprint.pprint(name_dict)
6
Robert

Ce n'est jamais une bonne idée de changer l'objet sur lequel vous faites une itération. Normalement, dict lève même une exception lorsque vous l'essayez:

name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
    name_dict.pop(k)

RuntimeError: la taille du dictionnaire a changé pendant l'itération

Cependant, dans votre cas, vous ajoutez un élément pour chaque élément supprimé. Cela le rend plus convolutionné. Pour comprendre ce qui se passe, vous devez savoir qu'un dictionnaire est un peu comme une table clairsemée. Par exemple, un dictionnaire tel que {1: 1, 3: 3, 5: 5} pourrait ressembler à ceci (cela a changé dans Python 3.6, pour les versions 3.6 et plus récentes, ce qui suit n'est plus correct):

hash    key    value
   -      -        - 
   1      1        1
   -      -        - 
   3      3        3
   -      -        - 
   5      5        5
   -      -        - 
   -      -        - 
   -      -        - 

C'est aussi l'ordre dans lequel il est itéré. Ainsi, lors de la première itération, il ira au deuxième élément (où le 1: 1 est stocké). Supposons que vous modifiez la clé en 2 et supprimiez la clé 1, le dict aurait ressemblé à ceci:

hash    key    value
   -      -        - 
   -      -        - 
   2      2        1
   3      3        3
   -      -        - 
   5      5        5
   -      -        - 
   -      -        - 
   -      -        - 

Mais nous sommes toujours à la deuxième ligne, donc la prochaine itération passera à la prochaine entrée "non vide" qui est 2: 1. Oups ...

C'est encore plus compliqué avec des chaînes en tant que clés car les hachages de chaînes sont aléatoires (par session), ainsi l'ordre dans le dictionnaire est imprévisible.

En 3.6, la disposition interne a été légèrement modifiée, mais quelque chose de similaire se produit ici.

En supposant que vous ayez cette boucle:

name_dict = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}

for k, v in name_dict.items():
    # print(k, k+6, name_dict.__sizeof__())
    name_dict[k+6] = name_dict.pop(k)
    # print(name_dict)

La disposition initiale est comme ceci:

key   value
  1       1
  2       2
  3       3
  4       4
  5       5
  6       1

La première boucle supprime 1 mais ajoute 7. Comme les dictionnaires sont ordonnés en 3.6, cela insère un espace réservé où 1 était:

key   value
  -       -
  2       2
  3       3
  4       4
  5       5
  6       1
  7       2

Ceci continue jusqu'à ce que vous remplaciez 4 par 10.

key   value
  -       -
  -       -
  -       -
  -       -
  5       5
  6       1
  7       2
  8       3
  9       4
 10       5

Mais lorsque vous remplacez 5 par 11, le dictionnaire devra augmenter sa taille. Puis quelque chose de spécial se produit: les espaces réservés sont supprimés:

key   value
  6       6
  7       1
  8       2
  9       3
 10       4
 11       5

Donc, nous étions à la position 5 de la dernière itération et maintenant nous changeons de ligne 6. Mais la ligne 6 contient 11: 5 pour le moment. Oups ...

Ne changez jamais l’objet que vous parcourez: ne jouez pas avec les touches pendant l’itération (les valeurs sont correctes)!

Vous pourriez plutôt conserver une "table de traduction" (vous ne savez pas si cela ne respecte pas votre obligation "sans créer de nouveau dict", mais vous avez besoin d'une sorte de stockage pour que votre code fonctionne correctement) et renommer après la boucle:

translate = {}
for k, v in name_dict.items():
    print("This is the key: '%s' and this is the value '%s'\n" % (k, v) )
    new_key = input("Please enter a new key: ")
    translate[k] = new_key
    time.sleep(4)

for old, new in translate.items():
    name_dict[new] = name_dict.pop(old)
8
MSeifert

en python3, dict.items () n'est qu'un aperçu du dict. comme vous n'êtes pas autorisé à modifier une variable itérative lors d'une itération, vous n'êtes pas autorisé à modifier un dict lors d'une itération sur dict.items () . vous devez copier des éléments () dans une liste avant une itération

for k, v in list(name_dict.items()):
    ...
    name_dict[new_key] = name_dict.pop(k)

cela ne répond pas à votre exigence "pas de nouveau dict", même si la liste contient en fait une copie complète de toutes vos données.

vous pouvez relâcher un peu l'empreinte mémoire en copiant uniquement les clés

for k in list(name_dict):
    v = name_dict.pop(k)
    ...
    name_dict[new_key] = v

EDIT: grâce à Sven Krüger, il a évoqué la possibilité d’un problème de collision vieille-clé-nouvelle clé. dans ce cas, vous devez aller chercher

kv = list(name_dict.items())
name_dict.clear()
for k, v in kv :
    ...
    name_dict[new_key] = v

à propos, il y a un cas d'utilisation pour ne pas créer un nouveau dict, celui qui est en cours pourrait être référencé ailleurs.

2
stefan

Pour avoir un objet itérable dans votre mémoire de travail qui ne dépend pas de votre dictionnaire d'origine, vous pouvez utiliser la méthode fromkeys. Il est maintenant possible d'assigner de nouvelles clés avec vos anciennes valeurs. Mais il y a une chose que vous devez garder à l'esprit: vous ne pouvez pas attribuer une valeur à une nouvelle clé qui ne soit pas l'ancienne clé, alors que la nouvelle clé est également une autre clé de l'ancien jeu de clés.

Old_Keys = { old_key_1, old_key_2, ..., old_key_n }

Vous affectez donc la valeur liée à l'ancienne clé à la nouvelle clé.

old_key_1  ->  new_key_1 not in Old_Keys  # Okay!
old_key_2  ->  new_key_2 == old_key_4     # Boom!... Error!...

Soyez conscient de cela lorsque vous utilisez ce qui suit!

CODE

D = {'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}

for key in D.fromkeys(D) :
    new_key = raw_input("Old Key: %s, New Key: " % key)
    D[new_key] = D.pop(key)

print D

CONSOLE

Old Key: key1, New Key: abc

Old Key: key2, New Key: def

Old Key: key3, New Key: ghi

{"abc": 'val1', "def": 'val2', "ghi": 'val3'}
1
Sven Krüger