类定义中的泛型二进制操作



我正在Python 3中编写一个很小的线性代数模块,并且有许多二进制运算符需要定义。由于二元操作符的每个定义本质上都是相同的,只是操作符本身发生了变化,因此我希望通过只编写一次泛型二元操作符定义来节省一些工作。

例如:

class Vector(tuple):
    def __new__(self, x):
        super().__new__(x)
    # Binary operators
    def __add__(self, xs):
        try:
            return Vector(a + x for a, x in zip(self, xs)))
        except:
            return Vector(a + x for a in self)
    def __and__(self, xs):
        try:
            return Vector(a & x for a, x in zip(self, xs))
        except:
            return Vector(a & x for a in self)
    ... # mul, div, or, sub, and all other binary operations

以上的二进制操作符都具有相同的形式。只更改了操作员。我想知道我是否可以一次写所有的操作符,像这样:

def __bop__(self, xs):
    bop = get_bop_somehow()
    try:
        return Vector(bop(a, x) for a, x in zip(self, xs)))
    except:
        return Vector(bop(a, x) for a in self)

我听说Python可以用__getattr__方法做一些神奇的事情,我试着用它来提取操作符的名字,像这样:

def __getattr__(self, name):
    print('Method name:', name.strip('_'))
但是,不幸的是,这只在使用完整的方法名调用时有效,而在使用操作符时不起作用。如何编写一个通用的二进制运算符定义?

您可以使用类装饰器来改变您的类,并在工厂函数的帮助下将它们全部添加进去:

import operator
def natural_binary_operators(cls):
    for name, op in {
        '__add__': operator.add,
        '__sub__': operator.sub,
        '__mul__': operator.mul,
        '__truediv__': operator.truediv,
        '__floordiv__': operator.floordiv,
        '__and__': operator.and_,
        '__or__': operator.or_,
        '__xor__': operator.xor
    }.items():
        setattr(cls, name, cls._make_binop(op))
    return cls

@natural_binary_operators
class Vector(tuple):
    @classmethod
    def _make_binop(cls, operator):
        def binop(self, other):
            try:
                return cls([operator(a, x) for a, x in zip(self, other)])
            except:
                return cls([operator(a, other) for a in self])
        return binop

还有其他一些方法可以做到这一点,但总体思路仍然是一样的。

您可以使用operator模块完成此操作,该模块为您提供操作符的功能版本。例如,operator.and_(a, b)a & b相同。

所以return Vector(a + x for a in self)变成了return Vector(op(a, x) for a in self),你可以参数化op。您仍然需要定义所有的魔法方法,但是它们可以是简单的传递。

更新:

这可能会超级慢,但是你可以创建一个包含所有二进制方法的抽象类,并从它继承。

import operator
def binary_methods(cls):
    operator_list = (
        '__add__', '__sub__', '__mul__', '__truediv__', 
        '__floordiv__', '__and__', '__or__', '__xor__'
    )
    for name in operator_list:
        bop = getattr(operator, name)
        method = cls.__create_binary_method__(bop)
        setattr(cls, name, method)
    return cls
@binary_methods
class AbstractBinary:
    @classmethod
    def __create_binary_method__(cls, bop):
        def binary_method(self, xs):
            try:
                return self.__class__(bop(a, x) for a, x in zip(self, xs))
            except:
                return self.__class__(bop(a, x) for a in self)
        return binary_method
class Vector(AbstractBinary, tuple):
    def __new__(self, x):
        return super(self, Vector).__new__(Vector, x)

原始:

好了,我想我已经有了一个有效的解决方案(只在Python 2.X中测试过),它使用类装饰器来动态创建二进制方法。

import operator
def add_methods(cls):
    operator_list = ('__add__', '__and__', '__mul__')
    for name in operator_list:
        func = getattr(operator, name)
        # func needs to be a default argument to avoid the late-binding closure issue
        method = lambda self, xs, func=func: cls.__bop__(self, func, xs)
        setattr(cls, name, method)
    return cls
@add_methods
class Vector(tuple):
    def __new__(self, x):
        return super(self, Vector).__new__(Vector, x)
    def __bop__(self, bop, xs):
        try:
            return Vector(bop(a, x) for a, x in zip(self, xs))
        except:
            return Vector(bop(a, x) for a in self)

下面是一些用法示例:

v1 = Vector((1,2,3))
v2 = Vector((3,4,5))
print v1 * v2 
# (3, 8, 15)

可以__getattr__做一些神奇的事情,但是如果你可以避免这样做,那么我会-它开始变得复杂!在这种情况下,你可能需要覆盖__getattribute__,但请不要这样做,因为如果你开始乱写__getattribute__,你会咬自己的裤子。

您可以通过一种非常简单的方式实现这一点,只需定义第一个函数,然后在其他函数中执行__and__ = __add__

class MyClass(object):
    def comparison_1(self, thing):
        return self is not thing
    comparison_2 = comparison_1

A = MyClass()
print A.comparison_1(None)
print A.comparison_2(None)
print A.comparison_1(A)
print A.comparison_2(A)

$ python tmp_x.py 
True
True
False
False

然而,我不喜欢这种粗俗的行为。我只需要输入

class MyClass(object):
    def comparison_1(self, thing):
        "Compares this thing and another thing"
        return self is not thing
    def comparison_2(self, thing):
        "compares this thing and another thing as well"
        return self.comparison_1(thing)

为了清晰起见,最好多写几行。

<标题>编辑:

所以我尝试了__getattribute__,不工作:/。我承认我不知道为什么。

class MyClass(object):
    def add(self, other):
        print self, other
        return None
    def __getattribute__(self, attr):
        if attr == '__add__':
            attr = 'add'
        return object.__getattribute__(self, attr)

X = MyClass()
print X.__add__
X + X

不工作:/

andy@batman[18:15:12]:~$ p tmp_x.py 
<bound method MyClass.add of <__main__.MyClass object at 0x7f52932ea450>>
Traceback (most recent call last):
  File "tmp_x.py", line 15, in <module>
    X + X
TypeError: unsupported operand type(s) for +: 'MyClass' and 'MyClass'

最新更新