0

I'd like for an instance of my class to always be treated as though it is one of its attributes, unless other attributes are specifically requested. For instance, suppose my class is:

class Value:

    def __init__(self, value, description):
        self.value = value
        self.description = description

such that value is a float and description is a string. Now, I'd like for operations to always treat an instance of Value as Instance.value, unless Instance.description is specifically requested.

I'm aware of two solutions to this. The first is to implement numeric emulation into the class in the following way:

def __add__(self, other):
    return self.value + other

for every possible numerical operation. However, this seems unduly cumbersome -- what if someone wants to use a Value instance to multiply a list, and what about error messages, and all the other ways operating on a class instance instead of a basic float could go wrong? Do I really have to introduce class methods for every possible operational case?

The second solution is to redefine things in the following way:

class Value(float):
    pass

value1 = Value(8.0)
value1.description = 'The first value'
value2 = Value(16.3)
value2.description = 'The second value'
...
...

Now, the instance simply is the float value, and the descriptions are hardcoded in. But this also seems cumbersome and incredibly un-Pythonic.

Am I missing a really simple method for this? Or some kind of boolean flag somewhere I can just flick on? It seems like it would be desirable often enough for Python to have a way.

justadampaul
  • 979
  • 1
  • 7
  • 11

1 Answers1

1

The "simple solution" is your second answer - subclass whatever the value is you're trying to act as. You can still override __init__ and add whatever entities you need (though you also have to overwrite __new__ as well - see this answer):

class Value(float):
    def __new__(self, value, description=''):
        return float.__new__(self, value)
    def __init__(self, value, description=''):
        # let the superclass construct itself
        float.__init__(value)
        # give self a description
        self.description = description

value1 = Value(8.0, 'the first value')
value2 = Value(16.3, 'the second value')
print(value1 + value2)  # 24.3
print(value1 * value2)  # 130.4
print(value1.description)  # the first value
print(value2.description)  # the second value

This is also basically the same as your first possible solution, except that float has already defined all the arithmetic operators and you're just inheriting that. The downside here is that floats are immutable, so you can't change the value after you've already created the object.

It might also be possible to create a decorator that would do this, but that would be significantly more complicated.

Green Cloak Guy
  • 23,793
  • 4
  • 33
  • 53
  • Thank you! This is exactly what I was looking for. It works, but could you elaborate on the necessity of the `float.__init__(value)` line? It seems to work without this and I can't quite parse the logic of why that is there. – justadampaul Aug 06 '19 at 16:15
  • 1
    @justadampaul Standard OOP protocol - if you're inheriting from a superclass, then call the superclass's constructor/initializer before constructing/initializing yourself. In this case it's not strictly necessary since `float.__init__()` probably doesn't actually do anything (it's all handled by `float.__new__()`), but it's good practice at least and if `float` *did* do something in `__init__()` you would want that to be done to your object too. – Green Cloak Guy Aug 06 '19 at 16:19
  • Great. Thank you again, that makes sense. I was simply declaring `self.value = value` instead, and sure the result is the same but I can see how it is structurally less sound. Important distinction. I've selected your answer as correct. Cheers! – justadampaul Aug 06 '19 at 16:24