tags:

views:

125

answers:

5

I need to keep track of units on float and int values in Python, but I don't want to use an external package like magnitude or others, because I don't need to perform operations on the values. Instead, all I want is to be able to define floats and ints that have a unit attribute (and I don't want to add a new dependency for something this simple). I tried doing:

class floatwithunit(float):

    __oldinit__ = float.__init__

    def __init__(self, *args, **kwargs):
        if 'unit' in kwargs:
            self.unit = kwargs.pop('unit')
        self.__oldinit__(*args, **kwargs)

But this doesn't work at all:

In [37]: a = floatwithunit(1.,unit=1.)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/Users/tom/<ipython console> in <module>()

TypeError: float() takes at most 1 argument (2 given)

Any suggestions?
A: 

It looks like you need to check if kwargs is not None before you try to see if there is a label 'unit'.

Change your code to

 if kwargs and 'unit' in kwargs:

Updated answer:

don't pass kwargs to __oldinit__
Steve De Caux
+1  A: 

I think you mean

class floatwithunit(float):

rather than

def floatwithunit(float):
Emil H
good point ! I didn't see that
Steve De Caux
Thanks - I updated the question as I'm still having a problem
astrofrog
+6  A: 

You need to override __new__ (the "constructor proper", while __init__ is the "initializer"), otherwise float's __new__ gets called with extraneous arguments, which is the cause of the problem you're seeing. You don't need to call float's __init__ (it's a no-op). Here's how I'd code it:

class floatwithunit(float):

    def __new__(cls, value, *a, **k):
        return float.__new__(cls, value)

    def __init__(self, value, *args, **kwargs):
        self.unit = kwargs.pop('unit', None)

    def __str__(self):
        return '%f*%s' % (self, self.unit)

a = floatwithunit(1.,unit=1.)

print a

emitting 1.000000*1.0.

Alex Martelli
+8  A: 

You might be looking for something like this:

class UnitFloat(float):

    def __new__(self, value, unit=None):
       return float.__new__(self, value)

    def __init__(self, value, unit=None):
        self.unit = unit


x = UnitFloat(35.5, "cm")
y = UnitFloat(42.5)

print x
print x.unit

print y
print y.unit

print x + y

Yields:

35.5
cm
42.5
None
78.0
truppo
"Write that down," the King said to the jury, and the jury eagerly wrote down all three dates on their slates, and then added them up, and reduced the answer to shillings and pence. - Alice in wonderland - L. Carroll
Stefano Borini
A: 

Alex Martelli indeed pointed out the root of the problem. I always find __new__ quite confusing, however, so here's a working piece of example code:

class FloatWithUnit(float):
    def __new__(cls, *args, **kwargs):
        # avoid error in float.__new__
        # the original kwargs (with 'unit') will still be passed to __init__
        if 'unit' in kwargs:
            kwargs.pop('unit')
        return super(FloatWithUnit, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        self.unit = kwargs.pop('unit') if 'unit' in kwargs else None
        super(FloatWithUnit, self).__init__(*args, **kwargs)
taleinat