LoginSignup
5
1

More than 1 year has passed since last update.

`numpy.float64`の値は`json.dumps`で出力できるが、`numpy.int64`は`TypeError`が発生する

Last updated at Posted at 2023-05-13

環境

  • Python 3.11.2
  • pandas 2.0.1
  • numpy 1.24.2

はじめに

pandas.DataFrameから取得した情報を加工して、JSON出力したいです。

以下のPythonスクリプトを実行すると、json.dumpsで"TypeError: Object of type int64 is not JSON serializable"というエラーが発生しました。

sample.py
import pandas
from dataclasses import dataclass, asdict
import json


@dataclass
class Info:
    year: int
    score: int


df = pandas.DataFrame({"year": [2020, 2021, 2022], "score": [2, 4, 6]})
print(df.dtypes)

last_row = df.iloc[-1]
last_info = Info(last_row["year"], last_row["score"])

# last_infoを使って何かする
...
result = last_info
print(f"{type(result.year)=}, {type(result.score)=}")
print(json.dumps(asdict(result)))
$ python sample.py
year     int64
score    int64
dtype: object
type(result.year)=<class 'numpy.int64'>, type(result.score)=<class 'numpy.int64'>
Traceback (most recent call last):
  File "/home/vagrant/Documents/study/20230511/sample.py", line 25, in <module>
    print(json.dumps(asdict(result)))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/home/vagrant/.pyenv/versions/3.11.2/lib/python3.11/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type int64 is not JSON serializable

エラーの原因

numpy.int64intのサブクラスでないから、json.dumpsTypeErrorが発生しました。
もっとシンプルなコードで確認してみます。

In [27]: json.dumps(numpy.int64(1))
---------------------------------------------------------------------------
...
TypeError: Object of type int64 is not JSON serializable

In [28]: issubclass(numpy.int64, int)
Out[28]: False

numpy.intintのサブクラスでない理由

Pythonの整数は上限がなく65bit以上の整数を表現できます。一方numpy.int64は64bitまでしか整数を表現できません。したがって、numpy.int64intのサブクラスではありません。

numpy.int64以外はJSON出力できるのか?

json.dumps / json.dumpは以下の変換表に従って処理します。
image.png

numpy.int64以外のnumpyの型が、JSON出力できるかを確認しました。

numpy.float64

In [84]: issubclass(numpy.float64, float)
Out[84]: True

In [85]: json.dumps(numpy.float64(1.2))
Out[85]: '1.2'

Pythonのfloatは64bitで、numpy.float64と表現できるbit数が同じです。だからnumpy.float64floatのサブクラスになれているのかもしれません。
numpy.float64floatのサブクラスになっている明確な理由は分かりませんが、きっとサブクラスにした方がが便利だからでしょう。

numpy.bool_

In [86]: issubclass(numpy.bool_, bool)
Out[86]: False

In [87]: issubclass(bool, int)
Out[87]: True

boolintクラスのサブクラスです。したがって、numpy.bool_boolのサブクラスではありませんでした。

bool クラスは int クラスの派生クラスです (数値型 int, float, complex を参照してください)。このクラスからさらに派生することはできません。1

numpy.str_

In [88]: issubclass(numpy.str_, str)
Out[88]: True

In [89]: json.dumps(numpy.str_("a"))
Out[89]: '"a"'

numpy.ndarray

In [100]: issubclass(numpy.ndarray, list)
Out[100]: False

上述のエラーを解決する方法

(a) int()関数でintに変換する

last_info = Info(int(last_row["year"]), int(last_row["score"]))

(b) pandasのto_dictto_jsonを利用する

In [160]: last_row.to_json()
Out[160]: '{"year":2022,"score":6}'

In [161]: json.dumps(last_row.to_dict())
Out[161]: '{"year": 2022, "score": 6}'

(c) json.dumps関数のdefault引数に、変換する処理が記載された関数を指定する

default引数に指定した関数は、Serializeできないオブジェクトに対して呼ばれます。

sample1.py
import numpy
import json


def default(o):
    print(f"{type(o)=}")
    if isinstance(o, numpy.int64):
        return int(o)
    elif isinstance(o, numpy.bool_):
        return bool(o)
    elif isinstance(o, numpy.ndarray):
        return list(o)
    raise TypeError(repr(o) + " is not JSON serializable")


print(json.dumps(numpy.int64(1), default=default))
print(json.dumps(numpy.bool_(True), default=default))
print(json.dumps(numpy.array([1.2, 2.3]), default=default))
$ python sample1.py 
type(o)=<class 'numpy.int64'>
1
type(o)=<class 'numpy.bool_'>
true
type(o)=<class 'numpy.ndarray'>
[1.2, 2.3]

(d) json.dumps関数のcls引数に、JSONEncoderのサブクラスを指定する

import json
from json import JSONEncoder

class NumpyJSONEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, numpy.int64):
            return int(o)
        elif isinstance(o, numpy.bool_):
            return bool(o)
        elif isinstance(o, numpy.ndarray):
            return list(o)
        return JSONEncoder.default(self, o)

print(json.dumps(numpy.int64(1), cls=NumpyJSONEncoder))
print(json.dumps(numpy.bool_(True), cls=NumpyJSONEncoder))
print(json.dumps(numpy.array([1.2, 2.3]), cls=NumpyJSONEncoder))
$ python sample2.py 
1
true
[1.2, 2.3]

参考にしたサイト

  1. https://docs.python.org/ja/3/library/functions.html#bool

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1