2

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

Dave
  • 33
  • 6
  • Do you want to have your units preserved (and updated) by operations on your values? For instance, should `newVar(10, "m") / newVar(5, "s")` give another `newVar` instance with units of `m / s`? – Blckknght Apr 19 '14 at 19:20
  • 1
    You might want to use [a library](http://pint.readthedocs.org/en/latest/) here – Eric Apr 20 '14 at 22:53

3 Answers3

5

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
Community
  • 1
  • 1
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • Awesome! This looks like the exact answer to my question. I don't fully understand why the return from `__new__` cannot just be `return val`. Looks like I need to learn about subclassing – Dave Apr 19 '14 at 22:12
  • The issue of `__new__` vs `__init__` is a bit difficult. Basically, `__new__` gets called before `__init__` and **must** return some object. There are some good questions about its use here on SO, and you can find various tutorials around the net, see e.g. [here](http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html), [here](http://jaredgrubb.blogspot.it/2008/01/pythons-new-and-init.html) and [here](http://agiliq.com/blog/2012/06/__new__-python/). I must admit that I also don't understand all the details, I usually copy some example and modify it to my needs. – Bas Swinckels Apr 19 '14 at 22:38
0

In Python you can override many different operators, some of which are for emulating numeric types. You can find out more here.

In the case of multiplication, all you need to do is override __mul__(self, other) like so:

def __mul__(self, other):
    return self.value * other

You can also override other numeric operations like __add__(self, other) (for addition), __sub__(self, other) etc.

On a somewhat unrelated note, you might also want to think about what you want the behaviour be if one wanted to multiply to diameters together or add a weight to a diameter.

UPDATE

Based on your comment, you are probably better off overriding the the object's float representation as also mentioned by John, though it's not as clean as overriding the mathematical operators:

def __float__(self):
    return self.value
s16h
  • 4,647
  • 1
  • 21
  • 33
  • Thanks, I mentioned that. I plan to use all of the operations available to a float with this object (the simple multiplication was just an example.) I don't think that overriding every magic method makes sense. I expected there to be a single magic method that would override all math operations, but that does not seem to be the case. – Dave Apr 19 '14 at 17:44
0
class newVar():

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

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

    def __mul__(self, other):

        if hasattr(other, "value"):
            return self.value * other.value
        else:
            return self.value * other

diameter=newVar(10.0,'m')

print diameter 
print diameter*2

another possible solution would be to override the object float representation

class newVar():

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

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

    def __float__(self):

        return self.value

diameter=newVar(10.0,'m')

print diameter 
print float(diameter)*2
John
  • 13,197
  • 7
  • 51
  • 101
  • I hadn't thought about __ float __ but, I'm looking to be able to call just diameter with no modification or handling. If there is no other way I will just call diameter.value when doing math. – Dave Apr 19 '14 at 17:49