After seeing this question, I started wondering: is it possible to write a class that behaves like a random integer?
I managed to find some overridable methods with dir()
:
class RandomInt(int):
def __add__(self, other):
return randint(1, 100) + other
def __mul__(self, other):
return randint(1, 100) * other
def __div__(self, other):
return randint(1, 100) / other
def __sub__(self, other):
return randint(1, 100) - other
def __repr__(self):
return str(randint(1, 100))
But I feel like there's a more elegant way to inject randint(1, 100)
into each method that accepts a self
argument.
Is there a way to do this without re-writing the entire int
class from scratch?
Something like:
>>> x = RandomInt()
>>> x + 1
2
>>> x + 1
74
>>> x * 4
152
Description. The Random Integer Generator block generates uniformly distributed random integers in the range [0, M-1], where M is specified by the Set size parameter. Use this block to generate random binary-valued or integer-valued data.
A random number occurs in a specified distribution only when two conditions are met: The values are uniformly distributed over a defined interval or set, and it is impossible to predict future values based on past or present ones.
This is a different answer because it is very different from the other one that I posted. (and I felt this deserved to be separate)
The Code:
class RandomInt:
def __getattr__(self, name):
attr = getattr(int, name, '')
if attr != '':
def wrapper(*args, **kw):
return attr(random.randint(1, 100), *args, **kw)
return wrapper
else:
raise AttributeError(
"'{0}' object has no attribute '{1}'".format('RandomInt',name))
An example run:
>>> x = RandomInt()
>>> x
88
>>> 1 + x # __radd__
67
>>> x*100 # __mul__
1900
>>> x+5 # __add__
50
>>> x-1000 # __sub__
-945
>>> x//5 # __floordiv__
8
>>> float(x) # __float__
63.0
>>> str(x) # __str__
'75'
>>> complex(x) # __complex__
(24+0j)
>>> sum([x]*10)
573
There is scope for improvement:
>>> x + x
Traceback (most recent call last):
File "<pyshell#1456>", line 1, in <module>
x + x
TypeError: unsupported operand type(s) for +: 'instance' and 'instance'
Same for x*x
, x/x
, and similar
Another version this time, similar to @gatto's answer:
import random, inspect
class RandomInt:
def __init__(self):
def inject(attr):
def wrapper(*args, **kw):
args = list(args)
for i,x in enumerate(args):
if isinstance(x, RandomInt):
args[i] = x+0
return attr(random.randint(1,100), *args, **kw)
return wrapper
for name in dir(int):
attr = getattr(int, name)
if inspect.ismethoddescriptor(attr):
setattr(self, name, inject(attr))
And this one has support for:
>>> x + x
49
>>> x // x
2
>>> x * x
4958
>>> x - x
77
>>> x ** x
467056167777397914441056671494001L
>>> float(x) / float(x)
0.28
Yet another version, that uses class attributes to overcome the new-style/old-style problem (thanks @gatto):
import random, inspect
class RandomInt(object):
pass
def inject(attr):
def wrapper(*args, **kw):
args = list(args)
for i,x in enumerate(args):
if isinstance(x, RandomInt):
args[i] = random.randint(1,100)
return attr(*args, **kw)
return wrapper
for name in dir(int):
attr = getattr(int, name)
if inspect.ismethoddescriptor(attr):
setattr(RandomInt, name, inject(attr))
Output:
>>> x
86
>>> x
22
>>> x * x
5280
>>> [1] * x
[1, 1, 1, 1, 1, 1]
>>> x * '0123'
'0123012301230123'
>>> s[x] # s = '0123456789' * 10
'5'
import inspect
from random import randint
class SelfInjecter(type):
def __new__(self, *args, **kw):
cls = type(*args, **kw)
factory = cls.__factory__
def inject(attr):
def wrapper(self, *args, **kw):
return attr(factory(self), *args, **kw)
return wrapper
for name in dir(cls):
attr = getattr(cls, name)
if inspect.ismethoddescriptor(attr):
setattr(cls, name, inject(attr))
return cls
class RandomInt(int):
__metaclass__ = SelfInjecter
__factory__ = lambda self: randint(1, 100)
x = RandomInt()
print x + 3, x - 3, x * 3, repr(x)
The code above has a few problems.
As was suggested by Schoolboy, the following doesn't work properly:
>>> print x * x
0
We need to convert all arguments to our new type RandomInt
if possible:
def factory(x):
if isinstance(x, cls):
return cls.__factory__(x)
return x
def inject(attr):
def wrapper(*args, **kw):
args = [factory(x) for x in args]
kw = {k: factory(v) for k, v in kw}
return attr(*args, **kw)
return wrapper
Also sequence multiplication and indexing doesn't work as expected:
>>> [1] * x, x * '123', '123'[x]
([], '', '1')
This is because Python doesn't use __index__
for int
-inherited types:
class Int(int):
def __index__(self):
return 2
>>> x = Int(1)
>>> '012'[x], '012'[x.__index__()]
('1', '2')
Here is the code from Python 2.7.4 implementation:
/* Return a Python Int or Long from the object item
Raise TypeError if the result is not an int-or-long
or if the object cannot be interpreted as an index.
*/
PyObject *
PyNumber_Index(PyObject *item)
{
PyObject *result = NULL;
if (item == NULL)
return null_error();
if (PyInt_Check(item) || PyLong_Check(item)) {
Py_INCREF(item);
return item;
}
if (PyIndex_Check(item)) {
result = item->ob_type->tp_as_number->nb_index(item);
if (result &&
!PyInt_Check(result) && !PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__index__ returned non-(int,long) " \
"(type %.200s)",
result->ob_type->tp_name);
Py_DECREF(result);
return NULL;
}
}
As you can see, it checks for int
and long
first and only then tries to call __index__
.
Solution is to inherit from object
and clone/wrap attributes from int
, or actually I like Schoolboys's answer more, I guess it can be corrected in a similar manner as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With