eristical

たまに書きます

RubyとPythonにおけるクラスの演算子

RubyPythonでの 自作クラスと組み込みクラスの間の演算の定義に困ったのでメモ

RubyでもPythonでも 例えば a + b みたいな二項演算の式は a.+(b) とか a.__add__(b) みたいな感じで処理されるっぽい.
このとき,a が 自作クラスのときは自分で定義した演算子が呼ばれるので困らないけど, aが組込クラスでbが自作クラスのときはa演算子が呼ばれるのでbとの演算が定義されてなくて死ぬ.

Ruby

RubyNumericクラスの算術演算子で相手が知らないクラスの場合は,#coerceってメソッドを呼んでくれるらしい.

これを使えば下のコードのようにうまく定義できた.

class MyClass
    def initialize(val)
        @val = val
    end

    def +(other)
        if other.kind_of?(MyClass)
            return @val + other.val
        elsif other.kind_of?(Numeric)
            return @val + other
        else
            return TypeError
        end
    end

    def coerce(other)
        return [self, other]
    end

    attr_accessor :val
end

a = MyClass.new(1)
b = MyClass.new(2)
puts("1 + 1 = #{1 + 1}") # => 1 + 1 = 2
puts("a + b = #{a + b}") # => a + b = 3
puts("a + 1 = #{a + 1}") # => a + 1 = 2
puts("1 + a = #{1 + a}") # => 1 + a = 2

Python

Pythona.__add__(b) を呼んでみて,NotImplementedが帰ってきたら b.__radd__(a)を呼んでくれるらしい.

ということで,下のように実装できた.

class MyClass:
    def __init__(self, val):
        self.val = val

    def __add__(self, other):
        if isinstance(other, (int, float)):
            return self.val + other
        elif isinstance(other, MyClass):
            return self.val + other.val
        else:
            return NotImplemented

    def __radd__(self, other):
        return self.__add__(other)


a = MyClass(1)
b = MyClass(2)
print("1 + 1 =", 1 + 1)
print("a + b =", a + b)
print("a + 1 =", a + 1)
print("1 + a =", 1 + a)