重写python中的所有二进制运算符或其他尊重物理尺寸的方式?

问题描述

我正在构建从float继承的类,以在某些化学计算中尊重尺寸。例如:

class volume(float):
  def __init__(self,value):
    float._init_(value)

现在我的目标是:

  1. 在普通浮点数与volume实例之间使用+或-时引发错误
  2. 在两个卷实例之间使用+或-时,返回一个卷实例。
  3. 每当使用*(从两侧)和/(从左)时,返回卷的实例
  4. 从左开始使用/会引发错误
  5. 每当两个卷实例被分割时,返回浮点数。

现在,我将从左至右覆盖所有这四个运算符(例如_ add _和_ radd _);

err='Only objects of type volume can be added or subtracted together'
def __add__(self,volume2):
  if isinstance(volume2,volume): return volume(float(self)+volume2)
  else: raise TypeError(err)
def __radd__(self,volume): return volume(float(self)+volume2)
  else: raise TypeError(err)

是否有更简单的方法来访问所有这些对象,或者至少有一个表达式可以同时包含运算符的左右用法

解决方法

似乎这个问题主要是关于避免代码重复。关于乘法和除法情况,您的功能稍有不同,可能必须显式地编写单独的方法,但是对于与加法和减法有关的方法,以下技术将起作用。本质上是用猴子修补类,并且可以这样做,尽管您不应该尝试与Python 3中的猴子修补 instances 类似。

按照惯例,我用大写V调用了Volume类。

class Volume(float):
  pass

def _make_wrapper(name):
    def wrapper(self,other):
        if not isinstance(other,Volume):
            raise ValueError
        return Volume(getattr(float,name)(self,other))
    setattr(Volume,name,wrapper)

for _method in ('__add__','__radd__','__sub__','__rsub__',):                    
    _make_wrapper(_method)

在这些情况下,等效的显式方法如下所示,因此请按乘/除情况进行调整,但请注意显式使用float.__add__(self,other)而不是self + other,因为该问题提示您打算使用(问题提到volume(self+volume2)时使用),这将导致无限递归。

    def __add__(self,Volume):
            raise ValueError
        return Volume(float.__add__(self,other))        

关于__init__,我现在已经在上面删除了它,因为如果只需要调用float.__init__,那么就根本不需要定义它(只需简单地继承{{1} })。如果您想使用__init__方法来初始化 else ,那么是的,您 还需要包括对__init__的显式调用就像您在问题中所做的一样(尽管请注意双下划线–在您试图致电float.__init__的问题中)。

,

metaclass是控制class的构造方式。

您可以使用metaclasses重载所有数学运算符,如下所示:

err='Only objects of type volume can be added or subtracted together'

class OverLoadMeta(type):
    def __new__(meta,bases,dct):
        # this is the operation you want to use instead of default add or subtract.
        def op(self,volume2):
            if isinstance(volume2,Volume): 
                return Volume(float.__add__(self,volume2))
            else:
                raise TypeError(err)
        # you can overload whatever method you want here
        for method in ('__add__','__sub__'):
            dct[method] = op
        
        return super(OverLoadMeta,meta).__new__(meta,dct)


class Volume(float,metaclass=OverLoadMeta):
    ""


# you can use it like this:
a = Volume(1)
b = Volume(2)

c = a+b
print(c.__class__)
# class will be <class '__main__.Volume'>

a + 1
# raise TypeError: Only objects of type volume can be added or subtracted together