2017-09-19 108 views
8

Używam kilku punktów końcowych interfejsu API, które są bardzo szczegółowe w danych, które zwraca. Chciałbym podać podzbiór tych danych do innego fragmentu kodu w innym miejscu.Jak zamaskować zagnieżdżony słownik Pythona 3, aby zwrócić słownik zawierający tylko niektóre elementy?

Załóżmy mam podano kilka słowników tak (który mam zamiar pętli i filtr):

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

Chcę utworzyć funkcję, która będzie miała ten słownik w, wraz z „maska”, która zostanie użyty do utworzenia nowego słownika tylko dozwolonych elementów. To może być przykładem maski (choć mogę pracować z dowolnym formacie sprawia otrzymany kod najbardziej zwięzły):

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

Funkcja powinna następnie wrócić w tym:

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

Czy coś już zbudowany w Pythonie 3, które pomogłyby w tym pomóc? Wygląda na to, że jeśli będę musiał to zrobić ręcznie, stanie się to dość brzydkie. Znalazłem itertools.compress, ale wygląda na to, że jest to lista i nie radzi sobie ze złożonością słowników.

+0

Czy słyszałeś o ['jq (1)'] (https://stedolan.github.io/jq/)? – o11c

+0

Co powinno się stać, gdy klawisze maski nie mają pasującego klucza danych? –

Odpowiedz

3

można rekurencyjnie zbudować nową dict z maską wybierając jedynie wartości odpowiadające w głównym 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

Byłoby to dobra okazja, aby korzystać z rekurencji, tu jest jakiś przykładowy kod nie testowałem:

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

To będzie prawdopodobnie funkcji rekurencyjnej. Również na masce, polecam ten format: mask = ["id", "name", "model.id", "model.name", "model.manufacturer.name"]

Następnie chcesz najpierw zachować tylko wpisy, które są nazwane w masce:

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 

Następnie, w zależności od tego, czy chcesz usunąć sub-słowniki, które nie były w masce i nie miał elementy podrzędne, można dołączyć drugi etap:

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] 

kod końcowa: filterstage2(filterstage1(dictionary, mask), mask). Możesz łączyć ze sobą oba etapy, jeśli chcesz.

+0

P.S. Zobacz inne odpowiedzi; to rozwiązanie prawdopodobnie nie jest zbyt dobre dla kodu produkcyjnego: P – HyperNeutrino