67

I'm interested in subclassing the built-in int type in Python (I'm using v. 2.5), but having some trouble getting the initialization working.

Here's some example code, which should be fairly obvious.

class TestClass(int):
    def __init__(self):
        int.__init__(self, 5)

However, when I try to use this I get:

>>> a = TestClass()
>>> a
0

where I'd expect the result to be 5.

What am I doing wrong? Google, so far, hasn't been very helpful, but I'm not really sure what I should be searching for

2

2 Answers 2

86

int is immutable so you can't modify it after it is created, use __new__ instead

class TestClass(int):
    def __new__(cls, *args, **kwargs):
        return  super(TestClass, cls).__new__(cls, 5)

print TestClass()
Sign up to request clarification or add additional context in comments.

Comments

32

Though correct the current answers are potentially not complete.

e.g.

In [1]: a = TestClass()

In [2]: b = a - 5

In [3]: type(b)
Out[3]: <class 'int'>

Shows b as an integer, where you might want it to be a TestClass.

Here is an improved answer, where the functions of the base class are overloaded to return the correct type.

class positive(int):
    def __new__(cls, value, *args, **kwargs):
        if value < 0:
            raise ValueError("positive types must not be less than zero")
        return  super(cls, cls).__new__(cls, value)

    def __add__(self, other):
        res = super(positive, self).__add__(other)
        return self.__class__(max(res, 0))

    def __sub__(self, other):
        res = super(positive, self).__sub__(other)
        return self.__class__(max(res, 0))

    def __mul__(self, other):
        res = super(positive, self).__mul__(other)
        return self.__class__(max(res, 0))

    def __div__(self, other):
        res = super(positive, self).__div__(other)
        return self.__class__(max(res, 0))

    def __str__(self):
        return "%d" % int(self)

    def __repr__(self):
        return "positive(%d)" % int(self)

Now the same sort of test

In [1]: a = positive(10)

In [2]: b = a - 9

In [3]: type(b)
Out[3]: <class '__main__.positive'>

UPDATE:
Added __repr__ and __str__ examples so that the new class prints itself properly. Also changed to Python 3 syntax, even though OP used Python 2, to maintain relevancy.

UPDATE 04/22:
I found myself wanting to do something similar on two recent projects, one where I wanted an Unsigned type (i.e. x-y, where x is 0 and y is positive is still zero).

I also wanted a set-like type that was able to be updated and queried in a certain way.

The above method works but it's repetitive and tedious. What if there was a generic solution using metaclasses?

I could not find one so I wrote one. This will only work in recent Python (I would guess 3.8+, tested on 3.10)

First, the MetaClass

class ModifiedType(type):
    """
    ModifedType takes an exising type and wraps all its members
    in a new class, such that methods return objects of that new class.
    The new class can leave or change the behaviour of each
    method and add further customisation as required
    """

    # We don't usually need to wrap these
    _dont_wrap = {
    "__str__", "__repr__", "__hash__", "__getattribute__", "__init_subclass__", "__subclasshook__",
    "__reduce_ex__", "__getnewargs__", "__format__", "__sizeof__", "__doc__", "__class__"}

    @classmethod
    def __prepare__(typ, name, bases, base_type, do_wrap=None, verbose=False):
        return super().__prepare__(name, bases, base_type, do_wrap=do_wrap, verbose=verbose)

    def __new__(typ, name, bases, attrs, base_type, do_wrap=None, verbose=False):
        bases += (base_type,)

        #  Provide a call to the base class __new__
        attrs["__new__"] = typ.__class_new__

        cls = type.__new__(typ, name, bases, attrs)

        if "dont_wrap" not in attrs:
            attrs["dont_wrap"] = {}
        attrs["dont_wrap"].update(typ._dont_wrap)

        if do_wrap is not None:
            attrs["dont_wrap"] -= set(do_wrap)

        base_members = set(dir(base_type))
        typ.wrapped = base_members - set(attrs) - attrs["dont_wrap"]

        for member in typ.wrapped:
            obj = object.__getattribute__(base_type, member)
            if callable(obj):
                if verbose:
                    print(f"Wrapping {obj.__name__} with {cls.wrapper.__name__}")
                wrapped = cls.wrapper(obj)
                setattr(cls, member, wrapped)
        return cls

    def __class_new__(typ, *args, **kw):
        "Save boilerplate in our implementation"
        return typ.base_type.__new__(typ, *args, **kw)

An example usage to create a new Unsigned type

# Create the new Unsigned type and describe its behaviour
class Unsigned(metaclass=ModifiedType, base_type=int):
    """
    The Unsigned type behaves like int, with all it's methods present but updated for unsigned behaviour
    """
    # Here we list base class members that we won't wrap in our derived class as the
    # original implementation is still useful. Other common methods are also excluded in the metaclass
    # Note you can alter the metaclass exclusion list using 'do_wrap' in the metaclass parameters
    dont_wrap = {"bit_length", "to_bytes", "__neg__", "__int__", "__bool__"}
    import functools

    def __init__(self, value=0, *args, **kw):
        """
        Init ensures the supplied initial data is correct and passes the rest of the
        implementation onto the base class
        """
        if value < 0:
            raise ValueError("Unsigned numbers cannot be negative")

    @classmethod
    def wrapper(cls, func):
        """
        The wrapper handles the behaviour of the derived type
        This can be generic or specific to a particular method
        Unsigned behavior is:
            If a function or operation would return an int of less than zero it is returned as zero
        """
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            ret = func(*args, **kw)
            ret = cls(max(0, ret))
            return ret
        return wrapper

And some tests for the example

In [1]: from unsigned import Unsigned

In [2]: a = Unsigned(10)
   ...: print(f"a={type(a).__name__}({a})")
a=Unsigned(10)

In [3]: try:
   ...:     b = Unsigned(-10)
   ...: except ValueError as er:
   ...:     print(" !! Exception\n", er, "(This is expected)")
   ...:     b = -10  # Ok, let's let that happen but use an int type instead
   ...:     print(f" let b={b} anyway")
   ...:     
 !! Exception
 Unsigned numbers cannot be negative (This is expected)
 let b=-10 anyway

In [4]: c = a - b
   ...: print(f"c={type(c).__name__}({c})")
c=Unsigned(20)

In [5]: d = a + 10
   ...: print(f"d={type(d).__name__}({d})")
d=Unsigned(20)

In [6]: e = -Unsigned(10)
   ...: print(f"e={type(e).__name__}({e})")
e=int(-10)

In [7]: f = 10 - a
   ...: print(f"f={type(f).__name__}({f})")
f=Unsigned(0)

UPDATE for @Kazz:
To answer your question: "what do I need to do to make positive(10)*0.2 work?" Though it would be simpler to just int(u) * 0.2

Here is a small updated wrapper to handle the exception case e.g. (Unsigned * float) that serves as an example of how to modify behavior to match the desired subclass behaviour without having to individually overload each possible combination of argument types.

    # NOTE: also add '__float__' to the list of non-wrapped methods

    @classmethod
    def wrapper(cls, func):
        fn_name = func.__name__
        @cls.functools.wraps(func)
        def wrapper(*args, **kw):
            compatible_types = [issubclass(type(a), cls.base_type) for a in args]

            if not all(compatible_types):
                # Try converting
                type_list = set(type(a) for a in args) - set((cls.base_type, cls))
                if type_list != set((float,)):
                    raise ValueError(f"I can't handle types {type_list}")
                args = (float(x) for x in args)
                ret = getattr(float, fn_name)(*args, **kw)
            else:
                ret = func(*args, **kw)
                ret = cls(max(0, ret))
            return ret
        return wrapper

6 Comments

what do I need to do to make positive(10)*0.2 work ?
The whole point of this example is that operations on instances of this class return objects of the same class. Results of adding 0.2 (a float) to a positive type integer can't be of type positive, it makes no sense. I would suggest using the example given by @Anurag Uniyal, above where the resulting object can be of a different type.
The new generic version of my ansser handles your case (though I can't see a reason to use it)
Testing your code answer, I don't be able to subclass from Unsigned... How to do ?
If you want some Unsigned like behavior that is subtly different, why not just start with a ModifiedType and use that to express your special case?
To avoid repeating methods definitions that could be instead inherited (and follow the DRY principle)...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.