numpy arrayをlist経由で文字列化する理由
Python 3.8+NumPy 1.XからPython 3.10+NumPy 2.Xに移行する時の話です。一般にはあまりやらないことだと思うのですが、numpy arrayをあえてPythonビルトインのlist
にキャストして使いたい、という場面が時々あります。重宝していたのがnumpy arrayの文字列化の時です。numpy arrayを直接文字列化すると、スペース区切りで要素が並んでしまって使い勝手が悪かった一方で、list
を文字列化するとカンマ区切りで要素をリストしてくれました。少なくともPython 3.8+NumPy 1.Xの世界では。
>>> import numpy as np
>>> a = np.arange(10)
>>> print(a)
[0 1 2 3 4 5 6 7 8 9]
>>> print(list(a))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Python 3.10 + NumPy 2.X への移行
このような、print(list(a))
みたいなコードを含むモジュールをそのままPython 3.10+NumPy 2.Xに移行すると、大変なことになります。
>>> import numpy as np
>>> a = np.arange(10)
>>> print(a)
[0 1 2 3 4 5 6 7 8 9]
>>> print(list(a))
[np.int64(0), np.int64(1), np.int64(2), np.int64(3), np.int64(4), np.int64(5), np.int64(6), np.int64(7), np.int64(8), np.int64(9)]
Python 3.8までは、0
のようにただ数値だけが表示されていたのが、どういうわけか型情報まで表示してしまいます。親切心からの変更なのかもしれませんが、困ったことになりました。
正しい文字列化のお作法
numpy arrayを文字列化するには、numpy.array2string
を使うのが正解だと思われます。
numpy.array2string - NumPy API reference
separator
に', '
を指定すると、期待した結果が得られるようになりました。
>>> import numpy as np
>>> a = np.arange(10)
>>> print(np.array2string(a, separator=', '))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list
を経由するみたいな安易な方法を見つけてしまう前に、API referenceを読むべきでした。
2024/9/24 追記
np.ndarray.tolist
もこの用途に適していることがわかりました。
ndarray.tolist - NumPy API reference
ドキュメントによると、リスト変換時に各要素を元の型になるべく近いビルトインの型に変換してくれます。この性質により、文字列化したときに目的の出力が得られます。
>>> import numpy as np
>>> a = np.arange(10)
>>> print(a.tolist())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
どちらかというとarray2string
よりもtolist
の方がいいかもしれません。理由は、np.array2string
は「気が利きすぎる」のです。問題となるのは長大なリストを取り扱う場合で、巨大な文字列が生成されるのを避けるために間の要素が省略されてしまいます。あと、各要素に割り当てる文字数も自動調整されます。
>>> import numpy as np
>>> a = np.arange(10000)
>>> print(np.array2string(a, separator=', '))
[ 0, 1, 2, ..., 9997, 9998, 9999]
また、1行の長さが一定の文字数を超えると改行が入ります。
>>> import numpy as np
>>> a = np.arange(1000000000, 10000000001, 1000000000)
>>> print(np.array2string(a, separator=', '))
[ 1000000000, 2000000000, 3000000000, 4000000000, 5000000000,
6000000000, 7000000000, 8000000000, 9000000000, 10000000000]
threshold
やmax_line_width
パラメータにsys.maxsize
を設定することでこれらの挙動を無効化することはできますが、少なくとも上記のユースケースではシンプルにtolist
を使う方がベターかと思いました。
もちろんこれはユースケースに依存する話で、array2string
の親切さが身に染みる場合もあるかと思います。用途に応じて使い分けるようにしたいと思います。