2010-08-15 4 views
37

Was ist der effizienteste Weg, ein numpy Array mit simplejson zu serialisieren?SimpleJSON- und NumPy-Array

+0

[Verwandte] (http://stackoverflow.com/questions/11561932/why-does-json-dumpslistnp-arange5-Fehler-while-json-dumpsnp-arange5-tolis) a nd [einfache Lösung] (http://stackoverflow.com/questions/8230315/python-sets-are-not-json-serializable) durch explizite Übergabe eines [default handler] (http://docs.python.org/2 /library/json.html#json.dumps) für nicht serialisierbare Objekte. –

+0

Noch eine andere Antwort hier: http://stackoverflow.com/questions/26646362/numpy-array-is-not-json-serializable/32850511#32850511 – travelingbones

Antwort

26

I simplejson.dumps(somearray.tolist()) als die bequem Ansatz verwenden würde (wenn ich noch simplejson überhaupt verwendet wurde, die mit Python 2.5 oder früher stecken impliziert werden; 2.6 und später ein Standard-Bibliotheksmodul json haben, die auf die gleiche Weise funktioniert Natürlich würde ich das verwenden, wenn die Python-Version in Benutzung es unterstützt ;-).

Auf der Suche nach mehr Effizienz, Sie konnte Unterklasse json.JSONEncoder (in json, ich weiß nicht, ob die älteren simplejson bereits angeboten solche Anpassungsmöglichkeiten) und im default Verfahren, Sonderfall Instanzen numpy.array durch sie "in der Zeit" in Listen oder Tupel verwandeln. Ich bezweifle jedoch, dass Sie durch einen solchen Ansatz in Bezug auf die Leistung genug gewinnen würden, um den Aufwand zu rechtfertigen.

+0

JSONEncoders Standardmethode muss ein serialisierbares Objekt zurückgeben, so dass es dasselbe sein wird Rückgabe von 'somearray.tolist()'. Wenn Sie etwas schneller wollen, müssen Sie es Element für Element selbst codieren. –

10

Dies zeigt, wie aus einem 1D NumPy Array zu JSON und zurück in ein Array konvertieren:

try: 
    import json 
except ImportError: 
    import simplejson as json 
import numpy as np 

def arr2json(arr): 
    return json.dumps(arr.tolist()) 
def json2arr(astr,dtype): 
    return np.fromiter(json.loads(astr),dtype) 

arr=np.arange(10) 
astr=arr2json(arr) 
print(repr(astr)) 
# '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' 
dt=np.int32 
arr=json2arr(astr,dt) 
print(repr(arr)) 
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 

Aufbauend auf tlausch's answer, hier ist ein Weg, um JSON kodieren ein NumPy Array unter Beibehaltung Form und Typ eines beliebigen NumPy-Arrays - einschließlich solcher mit komplexem dtype.

class NDArrayEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, np.ndarray): 
      output = io.BytesIO() 
      np.savez_compressed(output, obj=obj) 
      return {'b64npz' : base64.b64encode(output.getvalue())} 
     return json.JSONEncoder.default(self, obj) 


def ndarray_decoder(dct): 
    if isinstance(dct, dict) and 'b64npz' in dct: 
     output = io.BytesIO(base64.b64decode(dct['b64npz'])) 
     output.seek(0) 
     return np.load(output)['obj'] 
    return dct 

# Make expected non-contiguous structured array: 
expected = np.arange(10)[::2] 
expected = expected.view('<i4,<f4') 

dumped = json.dumps(expected, cls=NDArrayEncoder) 
result = json.loads(dumped, object_hook=ndarray_decoder) 

assert result.dtype == expected.dtype, "Wrong Type" 
assert result.shape == expected.shape, "Wrong Shape" 
assert np.array_equal(expected, result), "Wrong Values" 
17

Ich fand diesen JSON-Unterklasse-Code für die Serialisierung eindimensionaler numpy Arrays innerhalb eines Wörterbuchs. Ich habe es versucht und es funktioniert für mich.

class NumpyAwareJSONEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, numpy.ndarray) and obj.ndim == 1: 
      return obj.tolist() 
     return json.JSONEncoder.default(self, obj) 

Mein Wörterbuch ist "Ergebnisse". Hier ist, wie ich in die Datei "data.json" schreiben:

j=json.dumps(results,cls=NumpyAwareJSONEncoder) 
f=open("data.json","w") 
f.write(j) 
f.close() 
+2

Dieser Ansatz funktioniert auch, wenn Sie innerhalb eines Diktats ein numpy-Array verschachtelt haben. Diese Antwort (denke ich) implizierte, was ich gerade gesagt habe, aber es ist ein wichtiger Punkt. –

+1

Das hat nicht für mich funktioniert. Ich musste 'return obj.tolist()' anstelle von 'return [x für x in obj]' verwenden. – nwhsvc

+0

Ich bevorzuge die Verwendung von numpy's Objekt, um zu listen - es sollte schneller sein, wenn numpy durch die Liste iteriert, anstatt dass python durch iteriert. –

2

Verbesserung der On Russ Antwort, würde ich auch die np.generic scalars:

class NumpyAwareJSONEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, np.ndarray) and obj.ndim == 1: 
       return obj.tolist() 
     elif isinstance(obj, np.generic): 
      return obj.item() 
     return json.JSONEncoder.default(self, obj) 
73

Um diese dtype und Dimension zu halten versuchen:

import base64 
import json 
import numpy as np 

class NumpyEncoder(json.JSONEncoder): 

    def default(self, obj): 
     """If input object is an ndarray it will be converted into a dict 
     holding dtype, shape and the data, base64 encoded. 
     """ 
     if isinstance(obj, np.ndarray): 
      if obj.flags['C_CONTIGUOUS']: 
       obj_data = obj.data 
      else: 
       cont_obj = np.ascontiguousarray(obj) 
       assert(cont_obj.flags['C_CONTIGUOUS']) 
       obj_data = cont_obj.data 
      data_b64 = base64.b64encode(obj_data) 
      return dict(__ndarray__=data_b64, 
         dtype=str(obj.dtype), 
         shape=obj.shape) 
     # Let the base class default method raise the TypeError 
     return json.JSONEncoder(self, obj) 


def json_numpy_obj_hook(dct): 
    """Decodes a previously encoded numpy ndarray with proper shape and dtype. 

    :param dct: (dict) json encoded ndarray 
    :return: (ndarray) if input was an encoded ndarray 
    """ 
    if isinstance(dct, dict) and '__ndarray__' in dct: 
     data = base64.b64decode(dct['__ndarray__']) 
     return np.frombuffer(data, dct['dtype']).reshape(dct['shape']) 
    return dct 

expected = np.arange(100, dtype=np.float) 
dumped = json.dumps(expected, cls=NumpyEncoder) 
result = json.loads(dumped, object_hook=json_numpy_obj_hook) 


# None of the following assertions will be broken. 
assert result.dtype == expected.dtype, "Wrong Type" 
assert result.shape == expected.shape, "Wrong Shape" 
assert np.allclose(expected, result), "Wrong Values" 
+6

Unklar für mich, warum das nicht mehr aufgewertet ist! – tacaswell

+0

Vereinbart, diese Lösung funktioniert im Allgemeinen für verschachtelte Arrays, IE ein Dictionary of Dictionary of Arrays. http://stackoverflow.com/questions/27909658/json-encoder-and-decoder-for-complex-numpy-arrays/27913569#27913569 –

+1

Dies ist einer dieser versteckten aber wertvollen SO-Edelsteine, die Sie Stunden und Stunden spart der Arbeit. –

3

Wenn Sie Russ Methode zu n-dimensionalen numpy Arrays anwenden möchten, können Sie versuchen, diese

class NumpyAwareJSONEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, numpy.ndarray): 
      if obj.ndim == 1: 
       return obj.tolist() 
      else: 
       return [self.default(obj[i]) for i in range(obj.shape[0])] 
     return json.JSONEncoder.default(self, obj) 

Dies verwandelt einfach ein n-dimensionales Array in eine Liste von Listen mit der Tiefe "n". Um solche Listen wieder in ein numpliges Array umzuwandeln, funktioniert my_nparray = numpy.array(my_list) unabhängig von der "Tiefe" der Liste.

1

Sie können auch diese Antwort mit nur einer Funktion in json.dumps auf diese Weise übergeben:

json.dumps(np.array([1, 2, 3]), default=json_numpy_serializer) 

Mit

import numpy as np 

def json_numpy_serialzer(o): 
    """ Serialize numpy types for json 

    Parameters: 
     o (object): any python object which fails to be serialized by json 

    Example: 

     >>> import json 
     >>> a = np.array([1, 2, 3]) 
     >>> json.dumps(a, default=json_numpy_serializer) 

    """ 
    numpy_types = (
     np.bool_, 
     # np.bytes_, -- python `bytes` class is not json serializable  
     # np.complex64, -- python `complex` class is not json serializable 
     # np.complex128, -- python `complex` class is not json serializable 
     # np.complex256, -- special handling below 
     # np.datetime64, -- python `datetime.datetime` class is not json serializable 
     np.float16, 
     np.float32, 
     np.float64, 
     # np.float128, -- special handling below 
     np.int8, 
     np.int16, 
     np.int32, 
     np.int64, 
     # np.object_ -- should already be evaluated as python native 
     np.str_, 
     np.timedelta64, 
     np.uint8, 
     np.uint16, 
     np.uint32, 
     np.uint64, 
     np.void, 
    ) 

    if isinstance(o, np.ndarray): 
     return o.tolist() 
    elif isinstance(o, numpy_types):   
     return o.item() 
    elif isinstance(o, np.float128): 
     return o.astype(np.float64).item() 
    # elif isinstance(o, np.complex256): -- no python native for np.complex256 
    #  return o.astype(np.complex128).item() -- python `complex` class is not json serializable 
    else: 
     raise TypeError("{} of type {} is not JSON serializable".format(repr(o), type(o))) 

validiert:

need_addition_json_handeling = (
    np.bytes_, 
    np.complex64, 
    np.complex128, 
    np.complex256, 
    np.datetime64, 
    np.float128, 
) 


numpy_types = tuple(set(np.typeDict.values())) 

for numpy_type in numpy_types: 
    print(numpy_type) 

    if numpy_type == np.void: 
     # complex dtypes evaluate as np.void, e.g. 
     numpy_type = np.dtype([('name', np.str_, 16), ('grades', np.float64, (2,))]) 
    elif numpy_type in need_addition_json_handeling: 
     print('python native can not be json serialized') 
     continue 

    a = np.ones(1, dtype=nptype) 
    json.dumps(a, default=json_numpy_serialzer) 
0

Eine schnelle, wenn auch nicht wirklich Optimaler Weg ist die Verwendung Pandas:

import pandas as pd 
pd.Series(your_array).to_json(orient='values') 
0

entdeckte ich nur die Antwort von tlausch auf diese Frage und erkannte, dass es das für mein Problem fast richtige Antwort gibt, aber zumindest für mich nicht funktioniert in Python 3.5, wegen mehrerer Fehler: 1 - unendliche Rekursion 2 - die Daten wurden als None gespeichert

, da es nicht direkt auf der ursprünglichen Antwort kommentieren kann doch, hier ist meine Version:

import base64 
import json 
import numpy as np 

    class NumpyEncoder(json.JSONEncoder): 
     def default(self, obj): 
      """If input object is an ndarray it will be converted into a dict 
      holding dtype, shape and the data, base64 encoded. 
      """ 
      if isinstance(obj, np.ndarray): 
       if obj.flags['C_CONTIGUOUS']: 
        obj_data = obj.data 
       else: 
        cont_obj = np.ascontiguousarray(obj) 
        assert(cont_obj.flags['C_CONTIGUOUS']) 
        obj_data = cont_obj.data 
       data_b64 = base64.b64encode(obj_data) 
       return dict(__ndarray__= data_b64.decode('utf-8'), 
          dtype=str(obj.dtype), 
          shape=obj.shape) 


    def json_numpy_obj_hook(dct): 
     """Decodes a previously encoded numpy ndarray with proper shape and dtype. 

     :param dct: (dict) json encoded ndarray 
     :return: (ndarray) if input was an encoded ndarray 
     """ 
     if isinstance(dct, dict) and '__ndarray__' in dct: 
      data = base64.b64decode(dct['__ndarray__']) 
      return np.frombuffer(data, dct['dtype']).reshape(dct['shape']) 
     return dct 

expected = np.arange(100, dtype=np.float) 
dumped = json.dumps(expected, cls=NumpyEncoder) 
result = json.loads(dumped, object_hook=json_numpy_obj_hook) 


# None of the following assertions will be broken. 
assert result.dtype == expected.dtype, "Wrong Type" 
assert result.shape == expected.shape, "Wrong Shape" 
assert np.allclose(expected, result), "Wrong Values"