【问题标题】:SimpleJSON and NumPy arraySimpleJSON 和 NumPy 数组
【发布时间】:2011-03-30 04:06:11
【问题描述】:

使用 simplejson 序列化 numpy 数组的最有效方法是什么?

【问题讨论】:

标签: python json numpy simplejson


【解决方案1】:

为了保持数据类型和维度,试试这个:

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
        super(NumpyEncoder, self).default(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"

【讨论】:

  • 同意,此解决方案通常适用于嵌套数组,即数组字典字典。 stackoverflow.com/questions/27909658/…
  • 你能采用它来处理recarrays吗? dtype=str(obj.dtype) 将recarray dtype 的列表截断为一个字符串,在重建时无法正确恢复该字符串,无需转换为字符串(即dtype=obj.dtype)我得到一个循环引用异常:-(
  • 这会安全地对数组的值进行编码,这很好。但是,如果您希望生成的 JSON 中的值是人类可读的,您可以考虑省略 base64 库并简单地转换为列表。可以在编码器中使用data_json = cont_obj.tolist(),在解码器中使用np.array(dct['__ndarray__'], dct['dtype']).reshape(dct['shape'])
  • @Community 这是为 C_CONTIGUOUS 编辑的,类似于我对stackoverflow.com/a/29853094/3571110 的回答。当我看到这个时,我认为 np.ascontiguousarray() 对 C_CONTIGUOUS 来说是无操作的,与简单地总是调用 np.ascontiguousarray() 相比,这使得 if/else 检查变得不必要。我说的对吗?
  • 为了解决无限递归问题,我将return json.JSONEncoder(self, obj)改为super(JsonNumpy, self).default(obj)
【解决方案2】:

我会使用simplejson.dumps(somearray.tolist()) 作为最方便 的方法(如果我仍然在使用simplejson,这意味着使用 Python 2.5 或更早版本;2.6 及更高版本有一个标准库模块json 的工作方式相同,所以如果使用中的 Python 版本支持它,我当然会使用它;-)。

为了提高效率,您可以子类化 json.JSONEncoder(在 json;我不知道旧的 simplejson 是否已经提供了这样的自定义可能性)并且,在default 方法,numpy.array 的特例实例,通过“及时”将它们转换为列表或元组。不过,我有点怀疑,通过这种方法,您是否会在性能方面获得足够的收益来证明这种努力是合理的。

【讨论】:

  • JSONEncoder 的默认方法必须返回一个可序列化的对象,因此它与返回somearray.tolist() 相同。如果你想要更快的东西,你必须自己逐个元素地对其进行编码。
【解决方案3】:

我发现这个 json 子类代码用于序列化字典中的一维 numpy 数组。我试过了,它对我有用。

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)

我的字典是“结果”。这是我写入文件“data.json”的方式:

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

【讨论】:

  • 当你有一个嵌套在字典中的 numpy 数组时,这种方法也有效。这个答案(我认为)暗示了我刚才所说的,但这是很重要的一点。
  • 这对我不起作用。我不得不使用return obj.tolist() 而不是return [x for x in obj]
  • 我更喜欢使用 numpy 的对象来列出 - 让 numpy 遍历列表应该比让 python 遍历更快。
  • and obj.ndim == 1 的意义何在?即使没有这个限制,这也有效
【解决方案4】:

这显示了如何从一维 NumPy 数组转换为 JSON 并返回到数组:

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])

tlausch's answer 为基础,这里 是一种对 NumPy 数组进行 JSON 编码同时保留任何形状和 dtype 的方法 NumPy 数组——包括具有复杂 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"

【讨论】:

    【解决方案5】:

    我刚刚发现了 tlausch 对这个问题的回答,并意识到它为我的问题提供了几乎正确的答案,但至少对我来说,它在 Python 3.5 中不起作用,因为有几个错误: 1 - 无限递归 2 - 数据保存为无

    由于我还不能直接评论原始答案,这是我的版本:

    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"    
    

    【讨论】:

    • 解决方案对我有用,将result = json.loads(dumped, object_hook=json_numpy_obj_hook) 替换为result = json.load(dumped, object_hook=NumpyEncoder.json_numpy_obj_hook)
    【解决方案6】:

    如果你想将 Russ 的方法应用于 n 维 numpy 数组,你可以试试这个

    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)
    

    这将简单地将 n 维数组转换为深度为“n”的列表列表。要将此类列表转换回 numpy 数组,my_nparray = numpy.array(my_list) 将不管列表“深度”如何。

    【讨论】:

      【解决方案7】:

      改进 Russ 的答案,我还将包括 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)
      

      【讨论】:

        【解决方案8】:

        您也可以通过这种方式仅通过传递给json.dumps 的函数来回答这个问题:

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

        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)))
        

        已验证:

        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)
        

        【讨论】:

          【解决方案9】:

          使用Pandas 是一种快速但不是真正最佳的方法:

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

          【讨论】:

          • 这似乎只适用于一维数组,但pd.DataFrame(your_array).to_json(orient='values') 似乎适用于二维数组。
          猜你喜欢
          • 2011-12-08
          • 1970-01-01
          • 2021-07-01
          • 1970-01-01
          • 2021-11-24
          • 1970-01-01
          • 2018-06-02
          • 2020-08-02
          • 1970-01-01
          相关资源
          最近更新 更多