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
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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()
Method 2
Though correct the current answers are potentially not complete.
e.g.
In [1]: a = TestClass() In [2]: b = a - 5 In [3]: print(type(b)) <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]: print(type(b)) <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 the 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 can't 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(" !! Exceptionn", 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 can't 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. 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
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0