J'essaie d'analyser des données en Python. J'ai du JSON:
{
"data sources": [
"http://www.gcmap.com/"
],
"metros": [
{
"code": "SCL",
"continent": "South America",
"coordinates": {
"S": 33,
"W": 71
},
"country": "CL",
"name": "Santiago",
"population": 6000000,
"region": 1,
"timezone": -4
},
{
"code": "LIM",
"continent": "South America",
"coordinates": {
"S": 12,
"W": 77
},
"country": "PE",
"name": "Lima",
"population": 9050000,
"region": 1,
"timezone": -5
}
]
}
Si je voulais analyser le tableau "metros" dans un tableau d'objets Metro
de classe Python, comment pourrais-je configurer la classe?
Je pensais:
class Metro(object):
def __init__(self):
self.code = 0
self.name = ""
self.country = ""
self.continent = ""
self.timezone = ""
self.coordinates = []
self.population = 0
self.region = ""
Je souhaite donc parcourir chaque métro, placer les données dans un objet Metro
correspondant et placer cet objet dans un tableau d'objets Python ... Comment puis-je parcourir les métros JSON?
Si vous obtenez toujours les mêmes clés, vous pouvez utiliser **
pour construire facilement vos instances. Faire de la Metro
une namedtuple
vous simplifiera la vie si vous l'utilisez simplement pour conserver des valeurs:
from collections import namedtuple
Metro = namedtuple('Metro', 'code, name, country, continent, timezone, coordinates, population, region')
alors simplement
import json
data = json.loads('''...''')
metros = [Metro(**k) for k in data["metros"]]
En supposant que vous utilisez json pour charger les données, je voudrais utiliser une liste de namedtuple ici pour stocker les données sous la clé 'metro'
>>> from collections import namedtuple
>>> metros = []
>>> for e in data[u'metros']:
metros.append(namedtuple('metro', e.keys())(*e.values()))
>>> metros
[metro(code=u'SCL', name=u'Santiago', country=u'CL', region=1, coordinates={u'S': 33, u'W': 71}, timezone=-4, continent=u'South America', population=6000000), metro(code=u'LIM', name=u'Lima', country=u'PE', region=1, coordinates={u'S': 12, u'W': 77}, timezone=-5, continent=u'South America', population=9050000)]
>>>
Il est relativement facile à faire puisque vous avez lu les données avec json.load()
qui, dans ce cas, renverra un dictionnaire Python pour chaque élément dans les "métros" - parcourez-le simplement et créez la liste des instances de Metro
class. J'ai modifié la séquence d'appel de la méthode Metro.__init__()
pour faciliter la transmission des données à partir du dictionnaire renvoyé par json.load()
.
Puisque chaque élément de la liste "métros" du résultat est un dictionnaire, vous pouvez simplement le transmettre au constructeur de la classe Metro
en utilisant la notation **
pour le transformer en arguments de mot clé. Le constructeur peut alors simplement update()
c'est son propre __dict__
pour transférer ces valeurs sur lui-même.
En procédant de cette façon, au lieu d'utiliser quelque chose comme un collections.namedtuple
comme simple conteneur de données, Metro
est une classe personnalisée qui rend l'ajout des autres méthodes et/ou attributs que vous souhaitez trivial.
import json
class Metro(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __str__(self):
fields = [' {}={!r}'.format(k,v)
for k, v in self.__dict__.items() if not k.startswith('_')]
return '{}(\n{})'.format(self.__class__.__name__, ',\n'.join(fields))
with open('metros.json') as file:
json_obj = json.load(file)
metros = [Metro(**metro_dict) for metro_dict in json_obj['metros']]
for metro in metros:
print('{}\n'.format(metro))
Sortie:
Metro(
code='SCL',
continent='South America',
coordinates={'S': 33, 'W': 71},
country='CL',
name='Santiago',
population=6000000,
region=1,
timezone=-4)
Metro(
code='LIM',
continent='South America',
coordinates={'S': 12, 'W': 77},
country='PE',
name='Lima',
population=9050000,
region=1,
timezone=-5)
Utilisez le module json de la bibliothèque http://docs.python.org/2/library/json.html pour convertir le json en dictionnaire Python.
Peut-être quelque chose comme
import json
data = json.loads(<json string>)
data.metros = [Metro(**m) for m in data.metros]
class Metro(object):
def __init__(self, **kwargs):
self.code = kwargs.get('code', 0)
self.name = kwargs.get('name', "")
self.county = kwargs.get('county', "")
self.continent = kwargs.get('continent', "")
self.timezone = kwargs.get('timezone', "")
self.coordinates = kwargs.get('coordinates', [])
self.population = kwargs.get('population', 0)
self.region = kwargs.get('region', 0)
In [17]: def load_flat(data, inst):
....: for key, value in data.items():
....: if not hasattr(inst, key):
....: raise AttributeError(key)
....: else:
....: setattr(inst, key, value)
....:
In [18]: m = Metro()
In [19]: load_float(data['metros'][0], m)
In [20]: m.__dict__
Out[20]:
{'code': 'SCL',
'continent': 'South America',
'coordinates': {'S': 33, 'W': 71},
'country': 'CL',
'name': 'Santiago',
'population': 6000000,
'region': 1,
'timezone': -4}
Non seulement il est très lisible et très explicite à propos de ce qu'il fait, mais il fournit également une validation de base des champs (en soulevant des exceptions sur des champs incompatibles, etc.)