I\'d like to emulate overflow behavior of unsigned 4-bit integers, like this:
>>> x, y = Int4(10), Int4(9)
>>> x + y
Int4(3)
>>> x * y
No, subclassing int
will not automatically re-use the type when applying arithmetic to it:
>>> class Int4(int):
... def __new__(cls, i):
... return super(Int4, cls).__new__(cls, i & 0xf)
...
>>> x, y = Int4(10), Int4(9)
>>> x + y
19
>>> type(x + y)
<type 'int'>
You have to override the __add__
, etc. methods to cast back to Int4()
when you do this.
If you only ever want to support the type itself (e.g. not support converting other numeric types in the process), then you could can generate most of these:
from functools import wraps
class Int4(int):
def __new__(cls, i):
return super(Int4, cls).__new__(cls, i & 0xf)
def add_special_method(cls, name):
mname = '__{}__'.format(name)
@wraps(getattr(cls, mname))
def convert_to_cls(self, other):
bound_original = getattr(super(cls, self), mname)
return type(self)(bound_original(other))
setattr(cls, mname, convert_to_cls)
for m in ('add', 'sub', 'mul', 'floordiv', 'mod', 'pow',
'lshift', 'rshift', 'and', 'xor', 'or'):
add_special_method(Int4, m)
add_special_method(Int4, 'r' + m) # reverse operation
This produces methods that always return the type of self
from arithmetic special methods; this'll allow for further subclassing of Int4
as well.
Demo:
>>> from functools import wraps
>>> class Int4(int):
... def __new__(cls, i):
... return super(Int4, cls).__new__(cls, i & 0xf)
...
>>> def add_special_method(cls, name):
... mname = '__{}__'.format(name)
... @wraps(getattr(cls, mname))
... def convert_to_cls(self, other):
... bound_original = getattr(super(cls, self), mname)
... return type(self)(bound_original(other))
... setattr(cls, mname, convert_to_cls)
...
>>> for m in ('add', 'sub', 'mul', 'floordiv', 'mod', 'pow',
... 'lshift', 'rshift', 'and', 'xor', 'or'):
... add_special_method(Int4, m)
... add_special_method(Int4, 'r' + m) # reverse operation
...
>>> x, y = Int4(10), Int4(9)
>>> x + y
3
>>> x * y
10
This isn't as clever as @martijn-pieters' answer, but it does seem to work on python 2.7 and 3.*, whereas I get AttributeError: 'wrapper_descriptor' object has no attribute '__module__'
on python 2.7 with that answer.
import sys
lt_py3 = sys.version_info < (3,)
lt_py33 = sys.version_info < (3, 3)
class Int(int):
'''
int types
'''
def __new__(self, val=0):
return int.__new__(self, val & (1 << self.bits - 1) - 1)
def __max_type_bits(self, other):
'''
determine the largest type and bits available from those in `self` and
`other`
'''
if hasattr(other, 'bits'):
if self.bits < other.bits:
return type(other), other.bits
return type(self), self.bits
def __unary_typed(oper):
'''
return a function that redefines the operation `oper` such that the
result conforms to the type of `self`
'''
def operate(self):
return type(self)(oper(self))
return operate
def __typed(oper):
'''
return a function that redefines the operation `oper` such that the
result conforms to the type of `self` or `other`, whichever is larger
if both are strongly typed (have a `bits` attribute); otherwise return
the result conforming to the type of `self`
'''
def operate(self, other):
typ, bits = self.__max_type_bits(other)
return typ(oper(self, other))
return operate
def __unary_ranged(oper):
'''
return a function that redefines the operator `oper` such that the
result conforms to both the range and the type of `self`
'''
def operate(self, other):
'''
type and bitmask the result to `self`
'''
return type(self)(oper(self) & (1 << self.bits - 1) - 1)
return operate
def __ranged(oper):
'''
return a function that redefines the operator `oper` such that the
result conforms to both the range and the type of `self` or `other`,
whichever is larger if both are strongly typed (have a `bits`
attribute); otherwise return the result conforming to the type of
`self`
'''
def operate(self, other):
'''
type and bitmask the result to either `self` or `other` whichever
is larger
'''
typ, bits = self.__max_type_bits(other)
return typ(oper(self, other) & (1 << bits - 1) - 1)
return operate
# bitwise operations
__lshift__ = __ranged(int.__lshift__)
__rlshift__ = __ranged(int.__rlshift__)
__rshift__ = __ranged(int.__rshift__)
__rrshift__ = __ranged(int.__rrshift__)
__and__ = __typed(int.__and__)
__rand__ = __typed(int.__rand__)
__or__ = __typed(int.__or__)
__ror__ = __typed(int.__ror__)
__xor__ = __typed(int.__xor__)
__rxor__ = __typed(int.__rxor__)
__invert__ = __unary_typed(int.__invert__)
# arithmetic operations
if not lt_py3:
__ceil__ = __unary_typed(int.__ceil__)
__floor__ = __unary_typed(int.__floor__)
__int__ = __unary_typed(int.__int__)
__abs__ = __unary_typed(int.__abs__)
__pos__ = __unary_typed(int.__pos__)
__neg__ = __unary_ranged(int.__neg__)
__add__ = __ranged(int.__add__)
__radd__ = __ranged(int.__radd__)
__sub__ = __ranged(int.__sub__)
__rsub__ = __ranged(int.__rsub__)
__mod__ = __ranged(int.__mod__)
__rmod__ = __ranged(int.__rmod__)
__mul__ = __ranged(int.__mul__)
__rmul__ = __ranged(int.__rmul__)
if lt_py3:
__div__ = __ranged(int.__div__)
__rdiv__ = __ranged(int.__rdiv__)
__floordiv__ = __ranged(int.__floordiv__)
__rfloordiv__ = __ranged(int.__rfloordiv__)
__pow__ = __ranged(int.__pow__)
__rpow__ = __ranged(int.__rpow__)
class Int4(Int):
bits = 4
x, y = Int4(10), Int4(9)
print(x + y)
print(x*y)
Running this code in a file called answer.py
produces
$ python2.7 answer.py
3
2
$ python3.4 answer.py
3
2
Overriding the __add__
method is a good idea, because you can make your calculations look clearly. Int4(4) + Int4(7)
looks better than Int4(4).addTo(Int4(7))
(or something like this).
Here is the code that could help you:
class Int4:
def __init__(self, num): # initialising
self.num = self.cap(num)
def __str__(self):
return str(self.num)
def __repr__(self):
return "Int4(" + self.__str__() + ")"
def __add__(self, other): # addition
return Int4(self.cap(self.num + other.num))
def __sub__(self, other): # subtraction
return Int4(self.cap(self.num - other.num))
@staticmethod
def cap(num): # a method that handles an overflow
while num < 0:
num += 16
while num >= 16:
num -= 16
return num
And testing it:
>>> x,y,z = Int4(5), Int4(8), Int4(12)
>>> x
Int4(5)
>>> y
Int4(8)
>>> z
Int4(12)
>>> print x+y
13
>>> print z+y
4
>>> print x-z
9