非常に稀かもしれませんが, 一般的に使われる四則演算とは異なったルールで計算をしたいことがあるかと思います。
筆者は, 少し変わった暗号計算解くために
例えば, N桁M進数 を符号なしの循環数として大量に演算する必要があり, N digit Unsigned M decimalクラスを作ったことがありました。
その件については, こちらの記事で簡易実装例を載せておきました。
Pythonクラス継承とoperator overloadingで四則演算可能なMACアドレスクラスを自作する - werry-chanの日記.料理とエンジニアリング
そのような時には, operator overloadingを実装しましょう。
今回は分数を計算するためのfractionクラスを例に説明していきます。
fractionクラスは計算上の丸め,桁落ち誤差などを避けることが可能なクラスで, 標準ライブラリとしても実装されているものになります。
今回はこれを自作していこうと思います。
標準ライブラリの中身を実際には見ていないので, 異なる部分多数あるかと思いますが, 今回はoperator overloadingの実装がメインテーマなのでお目こぼしください
まずはfractionクラスを作成した時に呼ばれる初期化関数を作成します。
class fraction: def __init__(self, num: int, den: int): self.num = num self.den = den
Pythonは勝手に型推定されて変換されて面倒なので, 関数設定でint型しか入れれないように拘束します。
さらに, その後も勝手にfloat型に変換されないように気を付けましょう。
次に, 分数を扱うので, 入力された数値がきちんと約分された状態にしましょう。
約分はこの後もたくさん使うので, 早めに書いておくと素敵です。
import math class fraction: def __init__(self, num: int, den: int): self.num = num self.den = den self.transmission() #約分する def transmission(self): gcd_val = math.gcd(self.num, self.den) self.num = int(self.num / gcd_val) #型変換避けint() self.den = int(self.den / gcd_val) #型変換避けint()
では次に, きちんと約分が出来ているか確認ついでに, 表示関数を作成しましょう。
import math class fraction: def __init__(self, num: int, den: int): self.num = num self.den = den self.transmission() def transmission(self): gcd_val = math.gcd(self.num, self.den) self.num = int(self.num / gcd_val) self.den = int(self.den / gcd_val) def show(self): self.transmission() print(self.num, '/', self.den, end='') if __name__ == "__main__": f1 = fraction(-2,5) f1.show() # output -> -2 / 5 print(f1) # output -> <__main__.fraction object at 0x000001E7D6B54508>
これで, 今後の作業中においても適宜数値が確認できるようになりました。
ちなみにオリジナルのクラスなので, 普通にprint()しても数値は見えません。
では次は, 加算演算子を作成していきましょう。
自作したfractionクラスを
f_0 = f_0 + f_1, あるいは f_0 += f_1
のように扱いたい場合には, 演算子を自分で定義する必要があります。
def __add__(self, val): # valは上記のf_1にあたる
と自作の+演算子による結果を定義してやります。
import math import copy class fraction: def __init__(self, num: int, den: int): self.num = num self.den = den self.transmission() def transmission(self): gcd_val = math.gcd(self.num, self.den) self.num = int(self.num / gcd_val) self.den = int(self.den / gcd_val) def show(self): self.transmission() print(self.num, '/', self.den, end='') def __add__(self, val): if type(val) != self.__class__: # 演算相手が別の型の時は val_ = fraction(val, 1) # 型を合わせてやる, 元のvalを壊さない形で else: val_ = val #元のvalを壊さないように計算用の変数を用意してやる ret_val = copy.deepcopy(self) #同上 ret_val.num = (ret_val.num * val_.den) + (val_.num * ret_val.den) # 通分して分子を計算 ret_val.den *= val_.den # 分母も通分状態にする ret_val.transmission() # 最後に約分して返す return ret_val
分数の計算だったので, 若干癖のある計算かもしれないですが, 上記のような形になります。
まぁ小学生の範囲でも実装すると意外と面倒です。
毎回返す直前呼んでいる約分(transmission())関数でint型の分子, 分母になるようにしているので, 推定型変換への注意が少なく済んで良いですね。
筆者は, Pythonのバグの5割は型推定のせいだと思っています
それでは同様にして他の演算子なども作成してしまいましょう。
#edited 20230703, before __dev__(), after __truediv__()
# this code is werry-chann lib # this lib provide fraction class import copy import math class fraction: def __init__(self, num: int, den: int): self.num = num self.den = den self.transmission() def transmission(self): gcd_val = math.gcd(self.num, self.den) self.num = int(self.num / gcd_val) self.den = int(self.den / gcd_val) def show(self): self.transmission() print(self.num, '/', self.den, end='') def to_int(self): self.transmission() return int(self.num / self.den) def to_float(self): self.transmission() return float(self.num / self.den) def __add__(self, val): if type(val) != self.__class__: val_ = fraction(val, 1) else: val_ = val ret_val = copy.deepcopy(self) ret_val.num = (ret_val.num * val_.den) + (val_.num * ret_val.den) ret_val.den *= val_.den ret_val.transmission() return ret_val def __sub__(self, val): if type(val) != self.__class__: val_ = fraction(val, 1) else: val_ = val ret_val = copy.deepcopy(self) ret_val.num = (ret_val.num * val_.den) - (val_.num * ret_val.den) ret_val.den *= val_.den ret_val.transmission() return ret_val def __mul__(self, val): if type(val) != self.__class__: val_ = fraction(val, 1) else: val_ = val ret_val = copy.deepcopy(self) ret_val.num = ret_val.num * val_.num ret_val.den *= val_.den ret_val.transmission() return ret_val def __truediv__(self, val): #edited 20230703, before __dev__(), after __truediv__() if type(val) != self.__class__: val_ = fraction(val, 1) else: val_ = val ret_val = copy.deepcopy(self) ret_val.num = ret_val.num * val_.den ret_val.den = ret_val.den * val_.num ret_val.transmission() return ret_val if __name__ == "__main__": f1 = fraction(-2,5) print(f1) # <__main__.fraction object at 0x0000020364C2FFC8> f2 = fraction(1,20) f3 = f1 + f2 f3.show() # -7 / 20 f4 = f1 - 3 f4.show() # -17 / 5
以上で四則演算の実装が完了しました。
ここまで実装が出来た人であれば残りの比較演算子(==, <, >)などについても調べながらできると思います。
以下の参考文献1つ目のリンクには他の演算子についても記述してありますので, 参考になりましたら幸いです。
参考文献
Operator Overloading in Python - GeeksforGeeks
https://docs.python.org/3/reference/datamodel.htm
operator — Standard operators as functions — Python 3.11.4 documentation
2, 3番目のリンクが公式なのですが, なかなか初心者には厳しそうでした。