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

Вы говорите о динамически определении каждой функции (в списке функций добавить, мул, вычитание и т.д.) для возврата randint передается каждому методу? Изменить: нет смысла даже спрашивать, что это говорит об этом в вопросе – jamylak

+0

@jamylak: Что-то в этом роде. Пока конечный результат заключается в том, что значение «RandomInt» появляется случайным для каждого метода. – Blender

+0

@Blender: ah, для * каждый * способ. Да, тогда вам нужно создать методы .. И это будет работать, только если 'int' является левым операндом * или *, если левый операнд сам не определяет крючок для операции. –

ответ

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()) 

Пример Run

>>> x = RandomInt() 
>>> x * 3 
81 
>>> x + 3 
56 
>>> x - 4 
68 
>>> x/4 
2 
+1

Правильно, но тогда вам придется писать 'x() + 1' вместо' x + 1'. – Blender

+0

Это не «хакерский», но не отвечает на вопрос, так как это не действует как int – jamylak

+0

@Blender Нет, это все равно будет 'x + 1' со стороны пользователя. – pradyunsg

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+ а», это не будет работать – jamylak

+0

@jamylak AFAIK - единственная проблема, с которой моя первая реализация заключалась в возврате простого «int» вместо «RandomInt». Я не вижу, где проблема, касающаяся '1000 + a'. Это вызов '__radd__', а не' __add__', поэтому вам просто нужно добавить '' __radd __ ''в список методов, которые будут добавлены в класс. – Bakuriu

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 не использует __index__ для int -inherited типов:

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' 

Same для 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' fail =) – gatto

+0

+1 Хороший метод (хотя для заполнения требуется больше времени, чем просто написать весь класс), мне интересно, почему это только работает для классов старого стиля, но – jamylak

+0

@Schoolboy, что я имею в виду, добавляет 'object' в качестве базового класса, что сделает его классом нового стиля, но когда я попробовал, чтобы он не работал – jamylak

 Смежные вопросы

  • Нет связанных вопросов^_^