4

In Python, data types (like int, float) both represent a value, but also have some built-in attributes/functions/etc:

In [1]: a = 1.2

In [2]: a
Out[2]: 1.2

In [3]: a.is_integer()
Out[3]: False

Is it possible to reproduce this behavior within Python, e.g. define a class:

class Scalar:
    def __init__(self, value)
        self.value = value

    # other code ....

s = Scalar(1.2)

where I could have s return 1.2 (instead of typing s.value), and do things like a = s -> a = 1.2? The closest I can get to this behavior is adding something like:

def __getitem__(self, key=None):
    return self.value

and using a = s[()], but that doesn't look very good.

Bart
  • 9,825
  • 5
  • 47
  • 73
  • possible duplicate of [Python property callable](http://stackoverflow.com/questions/13029254/python-property-callable) – TigerhawkT3 Aug 07 '15 at 21:01
  • 1
    In particular, see the snippet with a `WeirdInteger` class. – TigerhawkT3 Aug 07 '15 at 21:01
  • 2
    This isn't a general solution, but you could make your `Scalar` class in your example work the way you want by simply subclassing float (or int, or whatever other scalar type you wanted to mimic). `class Scalar(float): pass; s = Scalar(1.2)` – Two-Bit Alchemist Aug 07 '15 at 21:05
  • See also: https://stackoverflow.com/questions/11024646/is-it-possible-to-overload-python-assignment – Two-Bit Alchemist Aug 07 '15 at 21:08
  • subclassing a type might work, I have to see if it works for my (more complicated..) problem, but I'll give it a try! – Bart Aug 07 '15 at 21:14
  • @julbra Did you get something working? I was interested to see what answer you accepted and you haven't accepted one yet. – Two-Bit Alchemist Aug 10 '15 at 17:40
  • @ Two-Bit Alchemist I didn't except an answer because none of them produced the behavior I was looking for. Subclassing e.g. float works partly, but it doesn't allow me to add other arguments (from your example, e.g. `s=Scalar(1.2, 'bla'`), and adding `__call__` comes close, but having to write e.g. `s()` to retrieve `value` isn't very nice. And accepting an answer that didn't answer my question seems wrong. – Bart Aug 11 '15 at 18:40

2 Answers2

4

where I could have s return 1.2 (instead of typing s.value)

In the console? Then implement the __repr__ method.

a = s -> a = 1.2

To avoid having to use a = s.value, you can implement __call__ and call the object:

>>> class Scalar:
...     def __init__(self, value):
...         self.value = value
...     def __repr__(self):
...         return str(self.value)
...     def __call__(self):
...         return self.value
... 
>>> s = Scalar(1.2)
>>> s
1.2
>>> a = s()
>>> a
1.2

Check the documentation about the data model on emulating numeric types.

For example:

class Scalar:
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return str(self.value)
    def __call__(self):
        return self.value
    def __add__(self, other):
        return Scalar(self.value + other.value)
    def __lt__(self, other):
        return self.value < other.value
    def ___le__(self, other):
        return self.value <= other.value
    def __eq__(self, other):
        return self.value == other.value
    def __ne__(self, other):
        return self.value != other.value
    def __gt__(self, other):
        return self.value > other.value
    def __ge__(self, other):
        return self.value >= other.value

Can be used like this:

>>> s1 = Scalar(1.2)
>>> s2 = Scalar(2.1)
>>> s1 + s2
3.3
>>> s1 < s2
True
>>> s1 > s2
False
>>> s1 != s2
True
>>> s1 <= s2
True
>>> s1 >= s2
False

There are also the __int__ and __float__ magic methods, which you can implement and use like this (this is more semantically correct):

>>> a = int(s)
>>> a = float(s)
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
Havok
  • 5,776
  • 1
  • 35
  • 44
  • Well, the `__call__` gets rid of one set of brackets, so that's an improvement ;-) If I can't find another solution and the suggestions from above (using `class Scalar(float)`) don't work, it might be an acceptable solution – Bart Aug 07 '15 at 21:16
  • `__repr__` is going to complain if you try to return anything but a string: `TypeError: __repr__ returned non-string (type float)`. This also won't affect assignment statements (`a = s`) as described in the question. – Two-Bit Alchemist Aug 07 '15 at 21:18
1

As far as I know, that's not possible for your a = s example. You would have to change the behavior of =, the assignment operator. The assignment operator doesn't really do anything to the object on the right, it just copies a reference to it (in the case of an object, at least).

In general, it is possible to change the behavior of built in operators for your custom classes using operator overloading, but Python doesn't provide this sort of option for assignment (=) because of how different it is from operators like addition (+) and even equality (==).

ezig
  • 1,219
  • 1
  • 10
  • 15
  • 1
    Why is this answer getting downvoted when it says essentially the exact same thing as all of the answers [here](https://stackoverflow.com/questions/11024646/is-it-possible-to-overload-python-assignment)? – Two-Bit Alchemist Aug 07 '15 at 21:21
  • @Two-BitAlchemist - It says "that's not possible" when basic subclassing makes it possible. – TigerhawkT3 Aug 07 '15 at 21:27
  • 3
    @TigerhawkT3 Kind of... It doesn't make it possible in the general case, and it doesn't make sense to subclass, say, string if this is the _only_ behavior you want. – Two-Bit Alchemist Aug 07 '15 at 21:44
  • This answer is correct. It only answers part of the question but since `=` can't be overridden OP can't get the complete behaviour they were after. – d0nut Aug 10 '15 at 17:31