Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sub-classing float type in Python, fails to catch exception in __init__()

On Python 2.5 I need to use float numbers with a modified __str__() method. Also I need to know when the constructor fails.

Why I can't catch exceptions raised from float.__init__()?

What is the best way to consult the numeric value of my derived float object? In my code I'm using float(self).

class My_Number(float):
    def __init__(self, float_string):
        try:
            super(My_Number, self).__init__(float_string)
        except (TypeError, ValueError):
            raise My_Error(float_string)

    def __str__(self):
        if int(float(self)) == float(self):
            return str(int(float(self)))
        else:
            return str(round(float(self), 2))


>>> n = My_Number('0.54353')
>>> print n
0.54

>>> n = My_Number('5.0')
>>> print n
5

>>> n = My_Number('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for float(): foo
like image 262
Ricardo Avatar asked Dec 20 '09 17:12

Ricardo


2 Answers

float is immutable, therefore its __init__, the initializer, is basically a no-op -- nothing substantial can happen there, because the self object cannot be altered (if it's actually an instance of float rather than of a subclass -- but of course float's own __init__ must operate on that assumption;-).

Therefore, all the action happens in __new__, the constructor proper, just like for other immutable types like int, str, tuple, and so on. It's a common mistake to believe that __init__ is a constructor: it's not, it takes an already-constructed object as its first argument, self, and "initializes" it (if feasible, i.e., if that self is mutable!-) -- the construction itself happens in __new__.

So, your float subclass should start:

class My_Number(float):
  def __new__(cls, float_string):
    try: return float.__new__(cls, float_string)
    except (TypeError, ValueError): raise My_Error(float_string)

and you can remove the __init__, which is not needed. Now:

>>> n = My_Number('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __new__
NameError: global name 'My_Error' is not defined

(of course, it would work even better if you did have a My_Error exception class defined;-).

like image 159
Alex Martelli Avatar answered Nov 12 '22 12:11

Alex Martelli


try __new__ instead:

class F(float):
    def __new__(cls, *arg, **kw):
        try:
            return float.__new__(cls, *arg, **kw)
        except ValueError:
            raise Exception("foo")

print F("3.5")            
print F("asdf")

Also "self" is a float already so no need to say float(self), just "self" will do:

def __str__(self):
    return "%.2f" % self
like image 35
zzzeek Avatar answered Nov 12 '22 11:11

zzzeek