随机化整数行为
Posted
技术标签:
【中文标题】随机化整数行为【英文标题】:Randomizing integer behavior 【发布时间】:2013-04-21 14:37:19 【问题描述】:看到this question后,我开始怀疑:是否可以编写一个行为类似于随机整数的类?
我设法用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))
但我觉得有一种更优雅的方法可以将randint(1, 100)
注入到每个接受self
参数的方法中。
有没有办法做到这一点,而无需从头开始重写整个 int
类?
类似:
>>> x = RandomInt()
>>> x + 1
2
>>> x + 1
74
>>> x * 4
152
【问题讨论】:
您是在谈论动态定义每个函数(在函数列表中添加、计算、减去等)以返回传递给每个方法的 randint?编辑:甚至没有必要问它在问题中所说的 @jamylak:类似的东西。只要最终结果是RandomInt
的“值”对于每种方法来说都是随机的。
@Blender:啊,对于 every 方法。是的,那么您需要创建方法。只有当int
是左侧操作数或时,如果左侧操作数本身没有为操作。
@Blender __repr__
只需要self
,不需要other
最短的答案是:Yes
,但随后我收到提示:Body must be at least 30 characters; you entered 3.
:)..
【参考方案1】:
一个想法是有一个__call__
方法,它返回一个随机数。
class RandomInt(int):
def __call__(self):
return random.randint(1, 100)
def __add__(self, other):
return self() + other
def __mul__(self, other):
return self() * other
def __div__(self, other):
return self() / other
def __sub__(self, other):
return self() - other
def __repr__(self):
return str(self())
示例运行
>>> x = RandomInt()
>>> x * 3
81
>>> x + 3
56
>>> x - 4
68
>>> x / 4
2
【讨论】:
对,但是你必须写x() + 1
而不是 x + 1
。
它不是“hacky”,但不回答问题,因为它不像 int
@Blender 不,在用户端它仍然是x + 1
。
@jamylak 为什么?
@Schoolboy:那么__call__
方法的作用是什么?【参考方案2】:
您可以在运行时附加方法:
def add_methods(*names):
def the_decorator(cls):
for name in names:
def the_function(self, other):
return cls(random.randint(0, 100))
setattr(cls, name, the_function)
return cls
return the_decorator
@add_methods('__add__', '__mul__', '__sub__')
class RandomInt(int):
pass
这允许您选择应该随机执行的方法。
请注意,您可能很想使用 __getattr__
或 __getattribute__
之类的东西来自定义访问属性的方式并避免在类中显式设置方法,但这不适用于特殊方法,因为它们会查找does not pass through the attribute-access methods.
【讨论】:
你不能只是`return random.randint(0, 100)`,你需要实际调用带有other
作为参数的基本函数。例如。如果我想做1000+ a
这行不通
@jamylak AFAIK 我第一次实现的唯一问题是返回一个普通的int
而不是RandomInt
。我看不出问题出在1000 + a
上。这是对__radd__
而非__add__
的调用,因此您只需在要添加到类的方法列表中添加'__radd__'
。【参考方案3】:
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)
上面的代码有一些问题。
正如Schoolboy 所建议的,以下内容无法正常工作:
>>> print x * x
0
如果可能,我们需要将所有参数转换为我们的新类型RandomInt
:
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
序列乘法和索引也不能按预期工作:
>>> [1] * x, x * '123', '123'[x]
([], '', '1')
这是因为 Python 不将 __index__
用于 int
继承的类型:
class Int(int):
def __index__(self):
return 2
>>> x = Int(1)
>>> '012'[x], '012'[x.__index__()]
('1', '2')
以下是 Python 2.7.4 实现的代码:
/* 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;
如您所见,它首先检查int
和long
,然后才尝试调用__index__
。
解决方案是从object
继承并从int
克隆/包装属性,或者实际上我更喜欢Schoolboys's answer,我想它也可以用类似的方式更正。
【讨论】:
【参考方案4】:这是一个不同的答案,因为它与我发布的另一个答案非常不同。 (我觉得这应该分开)
代码:
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))
运行示例:
>>> 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
还有改进的余地:
>>> 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'
x*x
、x/x
等类似
这次又是一个版本,类似@gatto's的回答:
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))
而且这个支持:
>>> x + x
49
>>> x // x
2
>>> x * x
4958
>>> x - x
77
>>> x ** x
467056167777397914441056671494001L
>>> float(x) / float(x)
0.28
另一个版本,它使用类属性来克服新式/旧式问题(感谢@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))
输出:
>>> x
86
>>> x
22
>>> x * x
5280
>>> [1] * x
[1, 1, 1, 1, 1, 1]
>>> x * '0123'
'0123012301230123'
>>> s[x] # s = '0123456789' * 10
'5'
【讨论】:
我喜欢你在float
中包裹 x 的方式,因为 x / 1.0
失败 =)
+1 不错的方法(尽管打字比再次编写整个类需要更长的时间),但我想知道为什么这只适用于旧式类
@Schoolboy 我的意思是尝试将object
添加为基类,这将使它成为一个新样式的类,但是当我尝试它不起作用时
@jamylak 我会在我参加的派对回到家时尝试一下
显然 Python 只使用类属性,当一个新样式的类对象传递给像 str
、int
等 BIF 时。你可以在this patch 中看到对__int__
的调用看起来像o->ob_type->tp_as_number->nb_int(object)
,这意味着__int__
属于一个类型,而不是一个实例。另一方面,旧式类具有默认的 nb_int
实现,checks 用于属性存在。以上是关于随机化整数行为的主要内容,如果未能解决你的问题,请参考以下文章