そういう趣旨の話がTwitter(現・X)に流れていたので、ちょっと気になって調べてみた。
というのも、数値+単位という構造を持つクラスを作るというのはすぐ思い付くが、演算を実行するたびにクラスのメソッドを呼ぶのはスマートではないな、と。
結論を書くと、例えば、
a = 3KiB
b = 4MiB
print(a + b)
というような書き方が出来るモジュールは見つからなかった。ひょっとすると存在するのかもしれないけれど、PyObjectの拡張だけではダメで、コードをパースするところから手を入れないと無理なのは確実なので、Pythonでは難しいだろうな、と推測。
他の言語、例えばF#ではUnits of Measureとして利用できるらしい。
Pint
Pintでは以下のように書ける。
import pint
ureg = pint.UnitRegistry()
a = 3 * ureg.KiB
b = 4 * ureg.MiB
a + b
<Quantity(4099.0, 'kibibyte')>
”KB”は受け付けないが、”kB”はOK。
import pint
ureg = pint.UnitRegistry()
a = 3 * ureg.kB
b = 4 * ureg.MiB
a + b
<Quantity(4197.304, 'kilobyte')>
bitmath
bitmathなら、こう。
import bitmath
a = bitmath.KiB(3)
b = bitmath.MiB(4)
a + b
KiB(4099.0)
bitmathもPint同様、”KB”は受け付けないが”kB”は受け付ける。
astropy
astropyは天文関連の物理に関する単位や演算をカバーしていて、バイナリも扱える。
from astropy import units
a = 3 * units.KiB
b = 4 * units.MiB
a + b
<Quantity 4099. Kibyte>
“KB”は受け付けないが、”kB”なら受け付ける点は、これまでと同じ。
numericalunits
numericalunitsというのもあるが、バイナリは実装されていない。ここで”kB”がエラーにならないのは、ボルツマン定数として解釈されているから。
from numericalunits import *
a = 3 * KiB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'KiB' is not defined
a = 3 * kB
b = 4 * mB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'mB' is not defined. Did you mean: 'kB'?
b = 4 * MB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'MB' is not defined. Did you mean: 'kB'?
そもそもコードを読む限り、numericalunitsは実装が良く無さそうで(精度を担保できない書き方が散見される)、科学技術計算には使えないように見えるし、単位をグローバル変数として定義していて、名前空間が汚染されまくる悪夢が簡単に予想できる。
si-units
si-unitsは実装途中でやめてしまった雰囲気がある。
from si_units import *
a = 3 * KiB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'KiB' is not defined. Did you mean: 'KB'?
a = 3 * KB
b = 4 * MiB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'MiB' is not defined. Did you mean: 'min'?
b = 4 * MB
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'MB' is not defined. Did you mean: 'KB'?
binary
binaryは単位変換だけを提供するので、このブログエントリーの趣旨からはちょっと外れる。convert_units()はTupleを返してるだけだし、”KiB” / “MiB”はエラーになる。
from binary import BinaryUnits, convert_units
a = convert_units(3, BinaryUnits.KB, BinaryUnits.KB)
b = convert_units(4, BinaryUnits.MB, BinaryUnits.KB)
a + b
(3.0, 'KiB', 4096.0, 'KiB')
units-of-measure
units-of-measureもあるようだが、macOS + Python 3.11ではpipでインストール出来なかったので、パス。
まとめ
Pintかastropyを使っておけ、といったところか。
計算式を文字列として受け取ってパースする関数を書くぐらいなら、Pythonではないがbcalを外部呼び出しするのもアリな気がしてきた。