【问题标题】: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. :)..

标签: python int subclass metaclass


【解决方案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;
        }
    }

如您所见,它首先检查intlong,然后才尝试调用__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*xx/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 只使用类属性,当一个新样式的类对象传递给像 strint 等 BIF 时。你可以在this patch 中看到对__int__ 的调用看起来像o-&gt;ob_type-&gt;tp_as_number-&gt;nb_int(object),这意味着__int__ 属于一个类型,而不是一个实例。另一方面,旧式类具有默认的 nb_int 实现,checks 用于属性存在。
    猜你喜欢
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2017-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-25
    相关资源
    最近更新 更多