web-dev-qa-db-ja.com

Pythonでの演算子のオーバーロード:さまざまな型とパラメーターの順序の処理

私はベクトル(つまり、数値のリスト)の数学演算を支援する単純なクラスを持っています。私のVectorは、Vectororスカラー(floatまたはint)の他のインスタンスで乗算できます。

他のより強く型付けされた言語では、2つのvectorsを乗算するメソッドと、vectorint/floatを乗算する別のメソッドを作成します。私はPython=にまだまだ慣れていないので、これをどのように実装するかわかりません。それを行うには、オーバーライド__mul__()を使用し、受信パラメータをテストするしかありません。 :

class Vector(object):
  ...
 def __mul__(self, rhs):
  if isinstance(rhs, Vector):
     ...
  if isinstance(rhs, int) or isinstance(rhs, float):
    ...

そのようにしても、Vectorに次のようなスカラーを掛ける必要があります。

v = Vector([1,2,3])

result = v * 7

乗算のオペランドの順序を逆にしたい場合はどうなりますか?

result = 7 * v

Pythonでそれを行う正しい方法は何ですか?

18
RobertJoseph

___rmul___も実装する必要があります。 int.__mul__(7, v)への最初の呼び出しが失敗すると、Pythonは次にtype(v).__rmul__(v, 7)を試行します。

_def __rmul__(self, lhs):
    return self * lhs  # Effectively, turn 7 * v into v * 7
_

Rawingが指摘するように、この定義に対して___rmul__ = __mul___を単純に書くことができます。 ___rmul___は、非可換乗算を可能にするために存在し、オペランドを逆にして___mul___に単に延期するだけでは不十分です。

たとえば、Matrixクラスを作成していて、ネストされたリストによる乗算をサポートしたい場合は、たとえば、

_m = Matrix(...)  # Some 2 x 2 matrix
n = [[1, 2], [3,4]]
p = n * m
_

ここで、listクラスはMatrixインスタンスでリストを複数にする方法を知らないため、list.__mul__(n, m)が失敗した場合、Python次にMatrix.__rmul__(m, n)を試します。ただし、_n * m_と_m * n_は一般に2つの異なる結果であるため、Matrix.__rmul__(m, n) != Matrix.__mul__(m, n); ___rmul___は少し実行する必要があります正しい答えを生成するための余分な作業。

18
chepner

逆の操作のための特別な方法 があります:

  • ___rmul___の逆の場合は___mul___
  • および___radd___の場合は___add___、
  • ...

これらは、通常の演算で左側の演算子がNotImplementedを返すときに呼び出されます(したがって、演算_2 + vector_instance_はまず_(2).__add__(vector_instance)_を試行しますが、これがNotImplementedを返す場合はvector_instance.__radd__(2)が呼び出されます)。

ただし、算術特殊メソッドでisinstanceチェックを使用しないと、コードの繰り返しが多くなります。

実際に___init___で特別なケースを作成し、スカラーからVectorへの変換を実装できます。

_class Vector(object):
    def __init__(self, x, y=None, z=None):
        if y is None and z is None:
            if isinstance(x, Vector):
                self.x, self.y, self.z = x.x, x.y, x.z
            else:
                self.x, self.y, self.z = x, x, x
        Elif y is None or z is None:
            raise ValueError('Either x, y and z must be given or only x')
        else:
            self.x, self.y, self.z = x, y, z

    def __mul__(self, other):
        other = Vector(other)
        return Vector(self.x*other.x, self.y*other.y, self.z*other.z)

    __rmul__ = __mul__   # commutative operation

    def __sub__(self, other):
        other = Vector(other)
        return Vector(self.x-other.x, self.y-other.y, self.z-other.z)

    def __rsub__(self, other):   # not commutative operation
        other = Vector(other)
        return other - self

    def __repr__(self):
        return 'Vector({self.x}, {self.y}, {self.z})'.format(self=self)
_

これは期待どおりに機能するはずです。

_>>> 2 - Vector(1, 2, 3)
Vector(1, 0, -1)

>>> Vector(1, 2, 3) - 2
Vector(-1, 0, 1)

>>> Vector(1, 2, 3) * 2
Vector(2, 4, 6)

>>> 2 * Vector(1, 2, 3)
Vector(2, 4, 6)
_

これは素早い汚いドラフトであったことに注意してください(いくつかのバグがある可能性があります)。私は、各算術演算で型を特別にケーシングすることなく、それをどのように解決できるかという「一般的なアイデア」を提示したかっただけです。

10
MSeifert