0

I sometimes want an object to be treated as a number. This is what I do:

class C(object):
def __init__(self):
    self.a = 1.23
    self.b = 'b'        

def __float__(self):
    return float(self.a)

c = C()

Explicit casting works:

works = float(c) + 3.141
correct_comparison = (float(c) < 1)
>>> correct_comparison
False

However, automatic (implicit) casting doesn't work

wrong_comparison = (c < 1)
>>> wrong_comparison
True

doesnt_work = c + 2.718 #TypeError exception is thrown

Is there a way to perform automatic casting in Python. How bad is this idea?

UPDATE @BrenBarn has pointed me to the "emulating numeric types" section in Python documentation. Indeed, it is possible to define every possible operator, which gives a lot of flexibility, but is also very verbose. It seems that automatic implicit casting is possible only if one defines all the relevant operators. Is there a way to make this less verbose?

Boris Gorelik
  • 29,945
  • 39
  • 128
  • 170
  • 3
    There is no implicit casting in that sense. You need to implement the operations yourself. See [the documentation](http://docs.python.org/2/reference/datamodel.html#emulating-numeric-types). In some cases, you may be able to inherit from `float` and thus inherit the builtin versions of math operations, but whether this will work depends on how complex your class is. – BrenBarn Feb 26 '14 at 07:29
  • See [this question](http://stackoverflow.com/questions/9057669/how-can-i-intercept-calls-to-pythons-magic-methods-in-new-style-classes) for a way of writing a proxy object which proxies the operator overloading through for an underlying type. This makes it less verbose when defining your class, but the mechanism to make this happen is more complex. It's a tradeoff that may depend on whether you are just making one class like this, or many. – BrenBarn Feb 26 '14 at 07:49

2 Answers2

3

As @BrenBarn said, you can use inheritance:

class SomeFloat(float):
    def __init__(self, *args):
        super(float, self).__init__(*args)
        self.b = 'b'

It will be not so verbose.

Alex Pertsev
  • 931
  • 4
  • 13
  • this doesn't work on Python3: `SomeFloat(3.14)` raises an exception: "TypeError: object.__init__() takes no parameters" – Boris Gorelik Mar 11 '14 at 05:56
  • That's strange, it must be working in python3 too. By the way, in python > 3.2 you can do: def __init__(self, *args): super().__init__(*args) – Alex Pertsev Mar 14 '14 at 09:42
1

This is not the way Python thinks about objects. There is little value coming casting C to a float because Python usually doesn't care what an object is but how an object behaves. You should implement the custom comparison functions like __lt__ and __eq__. See here. This will also be handy.

The solution may look something like

import functools

@functools.total_ordering
class C(object):

    def __init__(self):
        self.a = 1.2345

    def __float__(self):
        return float(self.a)

    def __eq__(self, other):
        return self.a == float(other)

    def __lt__(self, other):
        return self.a < float(other)

c = C()
assert c < 3.14
assert 3.14 > c
assert c == 1.23456
assert 1.23456 == c
assert c != 1
user2722968
  • 13,636
  • 2
  • 46
  • 67
  • This works with math.sin(c), but does not work with random.gauss(c, c), any clue as to why? – birdmw Dec 08 '16 at 21:21