5

Is it possible to make a user defined type be a virtual subclass of a built in type in python? I would like my class to be considered a subclass of int, however I don't want to inherit directly like this:

class MyInt(int):
    '''Do some stuff kind of like an int, but not exactly'''
    pass

Since then my class becomes effectively immutable, whether I want it to be or not. For instance, it becomes impossible to use methods like __iadd__ and __isub__ since int has no way to modify itself. I could inherit from numbers.Integral, but then when someone calls isinstance(myIntObj, int) or issubclass(MyInt, int) the answer will be False. I understand that classes with a metaclass of ABCMeta can use the method register to register classes as virtual baseclasses that don't truly inherit from them. Is there some way to do this with built in types? Something like:

registerAsParent(int, MyInt)

I have looked around (both in the python documentation and generally online) and haven't yet found anything close to what I am looking for. Is what I am asking for just completely impossible?

eestrada
  • 1,575
  • 14
  • 24

2 Answers2

1

Not sure what exactly what you are trying to do, as what you are asking is impossible as primitive types are essentially immutable. However you can override __iadd__ and such to return the result with the type you want. Note that I reversed the signs (used - instead of +) for drama.

>>> class MyInt(int):
...     def __iadd__(self, other):
...         return MyInt(self - other)
...     def __add__(self, other):
...         return MyInt(self - other)
... 
>>> i = MyInt(4)
>>> i += 1
>>> type(i)
<class '__main__.MyInt'>
>>> i
3
>>> i + 5
-2
>>> type(i + 5)
<class '__main__.MyInt'>

Rinse and repeat for the rest of the magic methods, which you would have need to done anyway to have a "proper" subclass of int (even if "virtual" users might expect them to function a certain way).

Oh, yeah, for extensibility (as if this wasn't insane already) use self.__class__ instead for the results

class MyInt(int):
    def __iadd__(self, other):
        return self.__class__(self - other)

So if we have another subclass of that.

>>> class MyOtherInt(MyInt):
...     def __iadd__(self, other):
...         return self.__class__(self + other)
... 
>>> i = MyOtherInt(4)
>>> i += 4
>>> i
8
>>> type(i)
<class '__main__.MyOtherInt'>
metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • Ok, yeah. I think that makes sense to me. Basically `__iadd__` does what a regular `int` does; do the operation and then replace the `MyInt` object that `i` was pointing to with a new `MyInt` object. For whatever reason, I had gotten it into my head that it was critical to modify the object. But returning a new copy effectively does the same thing. From a practical standpoint, this should work just fine. From a purely theoretical standpoint though, I am still interested to see if anyone knows of a way to make user types "virtual" sub-classes of built in types, immutable or otherwise. – eestrada Aug 05 '14 at 23:57
  • __iadd__ doesn't replace anything - it simply calculates and returns a new object. It is the rest of the code which does the replacement. Bear in mind if we do a += b in python a and b are just references to integer objects. – Tony Suffolk 66 Aug 06 '14 at 10:16
  • That is what I meant by replacing. Instead of mutating `self` and then returning `self` (which is what I had hoped to do originally, and what one would expect from a method call `__iadd__`), with metatoaster's answer a new object is returned, thus the object `i` was pointing to is replaced with a new object (although of the same type). I'm sorry if I was unclear. – eestrada Aug 06 '14 at 23:59
  • The underlying issue is that [`ints` are immutable in python](http://en.wikibooks.org/wiki/Python_Programming/Data_Types#Mutable_vs_Immutable_Objects), so what you are asking does not make sense/impossible in Python. – metatoaster Aug 07 '14 at 00:04
  • @eestrada Actually there is one way, but absolutely stupid and probably will break - use an internal attribute for `MyInt`, update `__init__` to set that (and also call `int.__init__` for compatibility) and implement all operations (and `__str__` and `__repr__`) so you can emulate the "in place" mutation, but this is well in the territory of needless work. Ask yourself __why__ you are doing this and whether this is _practical_ for what you are trying to achieve. – metatoaster Aug 07 '14 at 00:11
  • @metatoaster, so basically, use composition _and_ inheritance? I see what you mean by needless work. My guess is that most of the time, people just use duck-typing or introspection instead of checking for inheritance anyway, so putting in so much work becomes overkill. Chances are, if I _really_ need a class hierarchy, I will just inherit from numbers.Integral and go from there. – eestrada Aug 14 '14 at 01:50
  • @eestrada in a way, yes. Checking for inheritance usually is reserved for very strict checking and usually is overkill. Typically the pattern I use these days are composition rather than inheritance, especially when the inheritance model (i.e. subclassing) would introduce unneeded coupling that would make my code difficult to test. Glad that this worked out for you in the end and understand this part about Python. – metatoaster Aug 14 '14 at 01:57
  • IMO, this doesn't answer the OP's question as stated in the title of the question. – martineau Sep 18 '19 at 14:14
0

This is in fact partly possible: Indeed, numbers.Number does exactly this.

You can register an abstract class MyInt and then register it as a superclass of int like this:

from abc import ABCMeta, abstractmethod
from numbers import Integral


class MyInt(metaclass=ABCMeta):
    @abstractmethod
    def something_that_int_already_implements(self):
        ...


MyInt.register(Integral)  # superclass of int because numbers registered it
assert issubclass(int, MyInt)  # works
assert isinstance(0, MyInt)  # works

You can not however extend int this way (new methods will not be copied), just use it for subclass and instance checking.

palsch
  • 5,528
  • 4
  • 21
  • 32