PabloG
PabloG

Reputation: 26745

Extending base classes in Python

I'm trying to extend some "base" classes in Python:

class xlist (list):
    def len(self):
        return len(self)

    def add(self, *args):
        self.extend(args)
        return None


class xint (int):
    def add(self, value):
        self += value
        return self


x = xlist([1,2,3])
print x.len()   ## >>> 3 ok
print x         ## >>> [1,2,3] ok
x.add (4, 5, 6)
print x         ## >>> [1,2,3,4,5,6] ok

x = xint(10)
print x         ## >>> 10 ok
x.add (2)
print x         ## >>> 10  # Not ok (#1)

print type(x)         ## >>> <class '__main__.xint'> ok
x += 5
print type(x)         ## >>> <type 'int'>  # Not ok (#2)

It works fine in the list case because the append method modifies the object "in place", without returning it. But in the int case, the add method doesn't modify the value of the external x variable. I suppose that's fine in the sense that self is a local variable in the add method of the class, but this is preventing me from modifying the initial value assigned to the instance of the class.

Is it possible to extend a class this way or should I define a class property with the base type and map all the needed methods to this property?

Upvotes: 16

Views: 25550

Answers (5)

Raphael
Raphael

Reputation: 562

I wrote an example of a mutable integer class that implements some basic methods from the list of operator methods. It can print properly, add, subtract, multiply, divide, sort, and compare equality.

If you want it to do everything an int can you'll have to implement more methods.

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

    def _do_relational_method(self, other, method_to_run):
        func = getattr(self.value, method_to_run)
        if type(other) is MutablePartialInt:
            return func(other.value)
        else:
            return func(other)

    def __add__(self, other):
        return self._do_relational_method(other, "__add__")
    
    def __sub__(self, other):
        return self._do_relational_method(other, "__sub__")

    def __mul__(self, other):
        return self._do_relational_method(other, "__mul__")
    
    def __truediv__(self, other):
        return self._do_relational_method(other, "__truediv__")

    def __floordiv__(self, other):
        return self._do_relational_method(other, "__floordiv__")

    def __eq__(self, other):
       return self._do_relational_method(other, "__eq__")
    
    def __neq__(self, other):
       return self._do_relational_method(other, "__neq__")

    def __lt__(self, other):
        return self._do_relational_method(other, "__lt__")

    def __gt__(self, other):
        return self._do_relational_method(other, "__gt__")

    def __str__(self):
        return str(self.value)

    def __repr__(self):
        return self.__str__()

Upvotes: 0

John Douthat
John Douthat

Reputation: 41199

int is a value type, so each time you do an assignment, (e.g. both instances of += above), it doesn't modify the object you have on the heap, but replaces the reference with one of the result of the right hand side of the assignment (i.e. an int)

list isn't a value type, so it isn't bound by the same rules.

this page has more details on the differences: The Python Language Reference - 3. Data model

IMO, yes, you should define a new class that keeps an int as an instance variable

Upvotes: 5

gtmanfred
gtmanfred

Reputation: 593

i expanded you xlist class just a bit, made it so you could find all index points of a number making it so you can extend with multiple lists at once making it initialize and making it so you can iterate through it

class xlist:
    def __init__(self,alist):
        if type(alist)==type(' '):
            self.alist = [int(i) for i in alist.split(' ')]
        else:
            self.alist = alist
    def __iter__(self):
        i = 0
        while i<len(self.alist):
            yield self.alist[i]
            i+=1
    def len(self):
        return len(self.alist)
    def add(self, *args):
        if type(args[0])==type([1]):
            if len(args)>1:
                tmp = []
                [tmp.extend(i) for i in args]
                args = tmp
            else:args = args[0]
        if type(args)==type(''):args = [int(i) for i in args.split(' ')] 
        (self.alist).extend(args)
        return None
    def index(self,val):
        gen = (i for i,x in enumerate(self.alist) if x == val)
        return list(gen)

Upvotes: 3

dF.
dF.

Reputation: 75825

Your two xint examples don't work for two different reasons.

The first doesn't work because self += value is equivalent to self = self + value which just reassigns the local variable self to a different object (an integer) but doesn't change the original object. You can't really get this

>>> x = xint(10)
>>> x.add(2)

to work with a subclass of int since integers are immutable.

To get the second one to work you can define an __add__ method, like so:

class xint(int):
    def __add__(self, value):
        return xint(int.__add__(self, value))

>>> x = xint(10)
>>> type(x)
<class '__main__.xint'>
>>> x += 3
>>> x
13
>>> type(x)
<class '__main__.xint'>

Upvotes: 30

dwestbrook
dwestbrook

Reputation: 5278

Ints are immutable and you can't modify them in place, so you should go with option #2 (because option #1 is impossible without some trickery).

Upvotes: 0

Related Questions