2013-04-27 47 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