2

Given a simple class like

class Vector(object):
    def __init__(self, value):
        self.value = value
    def __abs__(self):
        return math.sqrt(sum([x**2 for x in self.value]))
    def __round__(self, *n):
        return [round(x,*n) for x in self.value]

why does abs(Vector([-3,4])) correctly yield 5 while round(Vector([-3.1,4])) complains with a TypeError: a float is required instead of the desired [-3,4], and how can this be fixed?

I know round should usually return a float, but for a vector as in this example there is probably no ambiguity on the possible meaning, so why can this not be simply overridden? Do I really have to subclass numbers.Real, or define Vector(...).round(n) instead?

Tobias Kienzler
  • 25,759
  • 22
  • 127
  • 221

1 Answers1

6

The __round__ special method was only introduced in Python 3. There is no support for the special method in Python 2.

You'll have to use a dedicated method instead of the function:

class Vector(object):
    def __init__(self, value):
        self.value = value

    def round(self, n):
        return [round(x, n) for x in self.value]

or you'd have to provide your own round() function:

import __builtin__

def round(number, digits=0):
    try:
        return number.__round__(digits)
    except AttributeError:
        return __builtin__.round(number, digits)

You could even monkey-patch this into the __builtins__ namespace:

import __builtin__

_bltin_round = __builtin__.round

def round(number, digits=0):
    try:
        hook = number.__round__
    except AttributeError:
        return _bltin_round(number, digits)
    else:
        # Call hook outside the exception handler so an AttributeError 
        # thrown by its implementation is not masked
        return hook(digits)

__builtin__.round = round
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I see, in that case I should tag the question [tag:python-2.7] as well... Does the same apply to `math.floor` et al.? And is there no `from __future__ import` to fix this? – Tobias Kienzler Apr 17 '13 at 11:24
  • No, unfortunately there is no `from __future__` switch that'll enable that behaviour. – Martijn Pieters Apr 17 '13 at 11:26
  • Bummer :-( That's one very convincing argument to switch to Python 3 for me then... – Tobias Kienzler Apr 17 '13 at 11:27
  • 1
    @TobiasKienzler Alternatively you could just monkey patch the `round` function (even though that's bad style) – jamylak Apr 17 '13 at 11:29
  • Note to self: _First_ store a reference to the original `round`, _then_ monkey-patch the wrapper which calls the _stored_ reference - otherwise it's [turtles all the way down](http://en.wikipedia.org/wiki/Turtles_all_the_way_down)... **edit** @MartijnPieters I see you also noticed that :-7 – Tobias Kienzler Apr 17 '13 at 11:48
  • I guess a less evil behaviour could be achieved by trying the builtin version first and only on a `TypeError` try the other one. But hey, doesn't this code basically do what a `from __future__ import round` would be expected to do? – Tobias Kienzler Apr 17 '13 at 12:04
  • Is `import __builtin__` actually necessary? One can simply access the original `round` via `__builtins__.round` – Tobias Kienzler Aug 14 '13 at 07:44
  • It is a CPython implementation detail that that is a available, really. – Martijn Pieters Aug 14 '13 at 07:46
  • Ah thanks, I found [this question](http://stackoverflow.com/q/11181519/321973). So in fact one should _never_ use `__builtins__` (with s), correct? **edit** Except in Python3, where [`__buildin__` is called `buildins`](http://stackoverflow.com/q/9047745/321973), but Python3 implements the "correct" `round` anyway... – Tobias Kienzler Aug 14 '13 at 07:47
  • 1
    @TobiasKienzler: exactly; and `__builtins__` (with an `s`) is a dictionary everywhere, *except* in the main script (`__name__ == '__main__'`) where it is a module object.. Using `__builtin__` makes it easier as you don't have to remember to handle either case. – Martijn Pieters Aug 14 '13 at 08:42