Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Can I have a subclass return instances of its own type for operators defined in a superclass?

In Python (2.7) I want to create a class of rational numbers which mimics the behavior of the Fraction class (in module fractions) but overrides the __repr__ method to match the result of __str__. The original idea was only for my own tinkering, and just in order to have IDLE output look friendlier. But now I am more interested in understanding the underlying inheritance/typing issue--which I think may be of general interest--as opposed to getting a workaround for this specific use case, which is admittedly trivial.

The problem is, I'd like to inherit the functionality of all numerical operators (methods __add__, __sub__, etc.) as well, but have the results be instances of my subclass rather than of Fraction. That's what I want, but instead, this happens:

class Q(Fraction):
    def __repr__(self):
        return str(self)

>>> Q(1,2)
1/2
>>> Q(1,2) + Q(1,3)
Fraction(5, 6)

This happens because the operators defined in Fraction return Fraction instances. Of course I could override all these magic methods individually, calling the parent class to do the math and then coercing to my type, but I feel there ought to be a way to handle such a repetitive situation generically (i.e. without writing "def" 20 times).

I also considered using __getattribute__ to intercept method calls, but this seems inelegant, extremely fragile, and pretty much guaranteed to fail in situations even marginally more complicated than this one. (I know __getattr__ is preferred but it seems that will NOT catch the method calls I am interested in since they ARE defined in the base class!)

Given that I'm not the author for the base class, is there a better way than overriding each method individually?

like image 421
Clint Givens Avatar asked Nov 10 '13 04:11

Clint Givens


1 Answers

it's a bit of work, but you can wrap it and create a delegator. I actually did something similar to what you did, to create an int that would print in hex by default. an better example from one of my own classes which subclasses int, to allow bit-sliced reads (writes obviously won't work, because ints are immutable, so this particular code didn't go far...). Maybe a lot of code for an example, but it shows how to use it:

# I stole this decorator from another stackoverflow recipe :) 
def returnthisclassfrom(specials):
  specialnames = ['__%s__' % s for s in specials.split()]
  def wrapit(cls, method):
    return lambda *a: cls(method(*a))
  def dowrap(cls):
    for n in specialnames:
      method = getattr(cls, n)
      setattr(cls, n, wrapit(cls, method))
    return cls
  return dowrap

def int_getslice(self, i, j):
    # NON-pythonic, will return everything inclusive i.e. x[5:3] returns 3 bits, not 2.
    # Because that's what users normally expect.
    # If you're a purist, modify below.
    if i > 1000000 or j > 1000000:
        raise ValueError, 'BitSize limited to 1 million bits'
    lo = min(i,j)
    hi = max(i,j)
    mask = (1<<((hi-lo)+1))-1

    return (self>>lo) & mask

def int_getitem(self, i):
    # Safety limit
    if i > 1000000:
        raise ValueError, 'BitSize limited to 1 million bits'
    return (self>>i)&1

def int_iter(self):
    # since getitem makes it iterable, override
    raise AttributeError, 'int object is not iterable'

@returnthisclassfrom('abs add and div floordiv lshift mod mul neg or pow radd rand rdiv rdivmod rfloordiv rlshift rmod rmul ror rpow rrshift rshift rsub rxor rtruediv sub truediv xor trunc')
class BitSliceInt(long):
  __getslice__ = int_getslice
  __getitem__ = int_getitem
  __iter__ = int_iter
like image 177
Corley Brigman Avatar answered Oct 16 '22 06:10

Corley Brigman