2017-09-19 4 views
8

Je consomme plusieurs points de terminaison d'une API qui est très verbeuse dans les données qu'elle renvoie. Je voudrais fournir un sous-ensemble de ces données à un autre morceau de code ailleurs.Comment masquer un dictionnaire imbriqué Python 3 pour renvoyer un nouveau dictionnaire avec seulement certains éléments?

Supposons que je me donne plusieurs dictionnaires comme celui-ci (que je prévois faire une boucle à travers et filtre):

asset = { 
    'id': 1, 
    'name': 'MY-PC', 
    'owner': 'me', 
    'location': 'New York City', 
    'model': { 
     'id': 1, 
     'name': 'Surface', 
     'manufacturer': { 
      'id': 1, 
      'name': 'Microsoft' 
     } 
    } 
} 

Je veux créer une fonction qui prendra ce dictionnaire, avec un « masque » qui sera utilisé pour créer un nouveau dictionnaire des seuls éléments autorisés. Cela pourrait être un masque par exemple (bien que, je peux travailler avec ce format permet le code résultant le plus concis):

mask = { 
    'id': True, 
    'name': True, 
    'model': { 
     'id': True, 
     'name': True, 
     'manufacturer': { 
      'name': True 
     } 
    } 
} 

La fonction doit alors retourner ceci:

mask = { 
    'id': 1, 
    'name': 'MY-PC', 
    'model': { 
     'id': 1, 
     'name': 'Surface', 
     'manufacturer': { 
      'name': 'Microsoft' 
     } 
    } 
} 

Y at-il quelque chose déjà construit en Python 3 qui aiderait à cela? Il semble que si je dois le faire manuellement, ça va devenir assez moche rapidement. J'ai trouvé itertools.compress, mais cela semble être pour des listes et ne gérera pas la complexité des dictionnaires.

+0

Avez-vous entendu parler de ['jq (1)'] (https://stedolan.github.io/jq/)? – o11c

+0

Qu'est-ce qui devrait arriver lorsque les clés du masque n'ont pas de clé de données correspondante? –

Répondre

3

Vous pouvez récursive construire une nouvelle dict du masque en sélectionnant uniquement les valeurs correspondant dans la principale dict:

def prune_dict(dct, mask): 
    result = {} 
    for k, v in mask.items(): 
     if isinstance(v, dict): 
      value = prune_dict(dct[k], v) 
      if value: # check that dict is non-empty 
       result[k] = value 
     elif v: 
      result[k] = dct[k] 
    return result 

print(prune_dict(asset, mask)) 

{'id': 1, 
'model': {'id': 1, 'manufacturer': {'name': 'Microsoft'}, 'name': 'Surface'}, 
'name': 'MY-PC'} 
2

Ce serait une bonne occasion d'utiliser la récursivité, voici quelques exemples de code que je ne l'ai pas testé:

def copy(asset, result, mask): 
    for key_name, value in mask.items(): 
     if value == True: 
      result[key_name] = asset[key_name] 
     else: 
      result[key_name] = x = {} 
      copy(asset[key_name], x, value) 

y = {} 
copy(asset, y, mask) 
0

Ce serait probablement une fonction récursive. En outre, pour le masque, je recommande ce format: mask = ["id", "name", "model.id", "model.name", "model.manufacturer.name"]

Ensuite, vous auriez d'abord garder uniquement les entrées qui sont nommées dans le masque:

def filterstage1(dictionary, mask): 
    result = {} 
    for key in dictionary: 
     if isinstance(dictionary[key], dict): 
      newmask = [maskname[mask.find(".") + 1:] for maskname in mask if maskname.startswith(key + ".")] 
      result[k] = filterstage1(dictionary[key], newmask) 
     elif key in mask: 
      result[key] = dictionary[key] 
    return result 

Ensuite, selon que vous souhaitez supprimer ou non sous-dictionnaires qui ne sont pas dans le masque et pas eu sous-éléments, vous pouvez inclure la deuxième étape:

def filterstage2(dictionary, mask): 
    result = {} 
    for key in dictionary: 
     if not (isinstance(dictionary[key], dict) and dictionary[key] == {} and key not in mask): 
      result[key] = dictionary[key] 

Code final: filterstage2(filterstage1(dictionary, mask), mask). Vous pouvez combiner les deux étapes ensemble si vous le souhaitez.

+0

P.S. Voir les autres réponses cette solution n'est probablement pas très agréable pour le code de production: P – HyperNeutrino