Search code examples
pythonmagic-methods

creating class object that normally looks like a float


I'm looking to roll my own simple object that can keep track of units for variables (maybe I'll be adding other attributes like tolerances too). Here is what I have so far:

class newVar():
    def __init__(self,value=0.0,units='unknown'):
        self.value=value
        self.units=units

    def __str__(self):
        return str(self.value) + '(' + self.units + ')'

    def __magicmethodIdontknow__(self): 
        return self.value


diameter=newVar(10.0,'m') #define diameter's value and units

print diameter #printing will print value followed by units

#intention is that I can still do ALL operations of the object
#and they will be performed on the self.value inside the object.

B=diameter*2 

Because I don't have the right magic method i get the following output

10.0(m)
Traceback (most recent call last):
  File "C:\Users\user\workspace\pineCar\src\sandBox.py", line 25, in <module>
     B=diameter*2 
TypeError: unsupported operand type(s) for *: 'instance' and 'int'

I guess i could override every magic method to just return self.value but that sounds wrong. Maybe I need a decorator?

Also, I know I can just call diameter.value but that seems repetative


Solution

  • I once tried to implement something similar myself, but never managed to finish it. One way is to implement a new class from scratch, which contains both the value and the units. But if you want to use it in calculations, you have to implement all the magic methods like __add__ and __mul__. An alternative is to sub-class float itself:

    class FloatWithUnit(float):
        def __new__(cls, val, unit):
            return float.__new__(cls, val)
        def __init__(self, val, unit):
            self.unit = unit
        def __str__(self):  
            return '%g %s' % (self, self.unit)
        def __repr__(self):
            return self.__str__()
    

    Subclassing float is apparently a bit tricky, so you have to implement __new__ in addition to __init__, see here for more discussion. When entering such an object on the command line, it shows its units:

    In [2]: g = FloatWithUnit(9.81, 'm/s^2')
    
    In [3]: g
    Out[3]: 9.81 m/s^2
    
    In [4]: type(g)
    Out[4]: __main__.FloatWithUnit
    

    But when used in caluations, it behaves like a normal float

    In [5]: g2 = 2 * g
    
    In [6]: g2
    Out[6]: 19.62
    
    In [7]: type(g2)
    Out[7]: float