我正在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'
标题>