在python类中动态添加对算术魔术函数的支持

时间:2019-06-16 11:06:27

标签: python python-3.x oop

假设我有一个python类A

class A:
    def __init__(self, matrix, metadata: list):
        self.matrix = np.array(matrix)
        self.metadata = metadata
    #... 

现在,我希望所有算术运算都适合我的班级。他们应该将操作简单地转换为matrix,即:

    def __add__(self, other):
        if isinstance(other, type(self)):
            raise ValueError("Not allowed.")
        else:
            return A(
                matrix=self.matrix.__add__(other),
                metadata=self.metadata,
            )

现在的问题是,我必须为每个算术魔术函数重复几乎相同的代码,即__add__, __sub__, __mul__, __truediv__, __pow__, __radd__, __rsub__, __rmul__, __rtruediv__, __iadd__, __isub__, __imul__, __itruediv__, __abs__, __round__, __floor__, __ceil__, __trunc__。这导致了很多重复的代码。

如何在for循环中动态定义它们?例如

magic_functions = ["__add__", "__sub__", ...]
for magic_function in magic_functions:
    # define the method and add it to the class

2 个答案:

答案 0 :(得分:0)

operator模块的目的是解决(广泛)这类问题:

import operator
def mkop(f):    # the usual scope for a closure
  def op(self,o):
    if isinstance(o,type(self)): raise …
    return type(self)(matrix=f(self.matrix,o),
                      metadata=self.metadata)
  return op
for op in ['add','sub',…]:
  setattr(A,"__%s__"%op,mkop(getattr(operator,op)))

在定义类时,您还可以使用locals()[…]=mkop(…)(这是其罕见的安全用途之一)进行上述操作。

答案 1 :(得分:0)

在这种情况下,我建议您使用decorator。可能不是很短,但是您将保存代码的可读性。

import numpy as np

def decorator(fn):
    def ret_fn(*args, **kwargs):
        if isinstance(args[1], type(args[0])):
            raise ValueError("Not allowed.")
        else:
            return fn(*args, **kwargs)

    return ret_fn

class A:
    def __init__(self, matrix, metadata: list):
        self.matrix = np.array(matrix)
        self.metadata = metadata

    @decorator
    def __add__(self, other):
        return A(
            matrix=self.matrix.__add__(othe),
            metadata=self.metadata,
        )

结果:

>>> a1 = A([[1], [2]], [])
>>> a2 = a1 + [[3], [4]]
>>> print(a2.matrix)
[[4]
 [6]]
>>> a1 + a1
Traceback (most recent call last):
...
    raise ValueError("Not allowed.")
ValueError: Not allowed.

我不知道您的函数之间有多少区别,但是您可以重写装饰器,并且该函数非常简单:

装饰器

def decorator(fn):
    def ret_fn(*args, **kwargs):
        if isinstance(args[1], type(args[0])):
            raise ValueError("Not allowed.")
        else:
            return A(
                    matrix=fn(*args, **kwargs),
                    metadata=args[0].metadata,
                )

    return ret_fn

方法

    @decorator
    def __add__(self, other):
        return self.matrix.__add__(other)