2013-04-27 45 views
7

看到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 
+0

你说的是动态定义每个功能(在功能列表中添加,MUL,减等)返回randint传递给每个方法?编辑:甚至没有要求它在问题中说明它 – jamylak 2013-04-27 14:20:08

+0

@jamylak:类似的东西。只要最终结果是RandomInt的“值”对每种方法都是随机的。 – Blender 2013-04-27 14:23:03

+0

@Blender:啊,用于*每个*方法。是的,那么你需要创建方法..只有当“int”是左侧操作数*或*时,如果左侧操作数本身没有为操作定义一个钩子,它才会起作用。 – 2013-04-27 14:36:16

回答

0

一个想法是有一个__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 
+1

没错,但是你必须写'x()+ 1'而不是'x + 1'。 – Blender 2013-04-27 14:26:30

+0

这不是“哈克”,但不回答这个问题,因为这不会像int一样行动 – jamylak 2013-04-27 14:35:28

+0

@Blender不,它仍然是用户端的“x + 1”。 – pradyunsg 2013-04-27 14:41:21

0

您可以在运行时附加的方法:

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

+0

你不能只''返回random.randint(0,100)'你实际上需要用'other'作为参数调用基本函数。例如。如果我想做'1000+ a''这不会工作 – jamylak 2013-04-27 14:43:16

+0

@jamylak AFAIK我的第一个实现的唯一问题是返回一个普通的'int'而不是'RandomInt'。我没有看到问题出现在“1000 + a”的问题上。这是对'__radd__'而不是'__add__'的调用,因此您只需在要添加到类中的方法列表中添加'__radd __''。 – Bakuriu 2013-04-27 15:07:02

1
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不为int -inherited类型使用__index__

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更多,我想这可以以类似的方式也被校正。

2

这是一个不同的答案,因为它与我发布的另一个截然不同。 (我觉得这理应是单独的)

的代码:

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' 
'' 
>>> s[x] # s = '' * 10 
'5' 
+1

我喜欢你如何将x包装在'float'中,因为'x/1.0'失败=) – gatto 2013-04-27 19:45:50

+0

+1 Nice方法(即使需要更长的输入时间而不是再次写入整个类),我想知道为什么只有这样虽然 – jamylak 2013-04-27 22:47:42

+0

@Schoolboy我的意思是尝试添加'object'作为基类,这将使它成为一个新的类,但是当我试图它不起作用 – jamylak 2013-04-28 07:04:39