【问题标题】:substituting __iadd__ doesn't work as expected for += operator对于 += 运算符,替换 __iadd__ 不能按预期工作
【发布时间】:2012-10-12 21:06:26
【问题描述】:

我有一个来自库 (numpy.ndarray) 的对象,其中我已将 _iadd_ 方法替换为自定义方法。 如果我调用 object._iadd_(x),它会按预期工作。但是, object+=x 似乎调用了旧的(未替代的)方法。 我想防止 numpy 在特定情况下发生溢出,所以我为此创建了一个上下文管理器。这是(仍然很粗略的)代码:

class NumpyOverflowPreventer( object ):
    inverse_operator= {'__iadd__':'__sub__', '__isub__':'__add__', '__imul__': '__div__', '__idiv__':'__mul__'}

    def _operate(self, b, forward_operator):
        assert type(b) in (int, float)
        reverse_operator= NumpyOverflowPreventer.inverse_operator[forward_operator]
        uro= getattr(self.upper_range, reverse_operator)
        lro= getattr(self.lower_range, reverse_operator)
        afo= self.originals[ forward_operator ]
        overflows= self.matrix > uro( b )
        underflows= self.matrix < lro( b )
        afo( b )
        self.matrix[overflows]= self.upper_range
        self.matrix[underflows]= self.lower_range
        
    def __init__(self, matrix):
        m= matrix
        assert m.dtype==np.uint8
        self.matrix= m
        self.lower_range= float(0)
        self.upper_range= float(2**8-1)
        
    def __enter__(self):
        import functools
        self.originals={}
        for op in NumpyOverflowPreventer.inverse_operator.keys():
            self.originals[ op ] = getattr( self.matrix, op )
            setattr( self.matrix, op, functools.partial(self._operate, forward_operator=op))
    
    def __exit__(self, type, value, tb):
        for op in NumpyOverflowPreventer.inverse_operator.keys():
            setattr( self.matrix, op, self.originals[ op ] )

运行这个:

a= np.matrix(255, dtype= np.uint8)
b= np.matrix(255, dtype= np.uint8)
with NumpyOverflowPreventer(a):
    a+=1
with NumpyOverflowPreventer(b):
    b.__iadd__(1)
print a,b

返回这个:

[[0]] [[255]]

【问题讨论】:

  • 看起来您正在实例上设置就地运算符方法。那是行不通的; Python 不会在实例上查找特殊方法,只会在类上查找。
  • @kindall 为什么不将其发布为答案? (只是好奇)
  • @VasiliyFaronov:因为我没有花时间真正深入理解代码或提出解决方案。
  • @kindall 确实,因此失败了,感谢您的澄清

标签: python numpy operator-keyword


【解决方案1】:

您看到的问题是未在实例上查找特殊的内置方法。它们在 matrix 类型上查找。所以在实例上替换它们不会导致它们被间接使用。

实现目标的一种方法是让NumpyOverflowPreventer 成为您要处理的操作的包装器...

import numpy as np 
import sys

class NumpyOverflowPreventer(object):

    inverse_operator= { 
        '__iadd__': '__sub__', 
        '__isub__': '__add__', 
        '__imul__': '__div__', 
        '__idiv__': '__mul__'
    }

    def __init__(self, matrix):
        m = matrix
        assert m.dtype==np.uint8
        self.matrix = m
        self.lower_range = float(0)
        self.upper_range = float(2**8-1)

    def __iadd__(self, v):
        # dynamic way to get the name "__iadd__"
        self._operate(v, sys._getframe().f_code.co_name)
        return self

    def _operate(self, b, forward_operator):
        assert type(b) in (int, float)
        reverse_operator = self.inverse_operator[forward_operator]
        uro= getattr(self.upper_range, reverse_operator)
        lro= getattr(self.lower_range, reverse_operator)
        afo= getattr(self.matrix, forward_operator)
        overflows= self.matrix > uro( b )
        underflows= self.matrix < lro( b )
        afo( b )
        self.matrix[overflows]= self.upper_range
        self.matrix[underflows]= self.lower_range

我在这里只定义了__iadd__,我相信你可以通过一些元类/装饰器动作动态地完成所有这些......但我保持简单。

用法:

a = np.matrix(255, dtype= np.uint8)
b = np.matrix(255, dtype= np.uint8)

p = NumpyOverflowPreventer(a)
p+=1

p = NumpyOverflowPreventer(b)
p.__iadd__(1)

print a,b
# [[255]] [[255]]

【讨论】:

  • "你看到的问题是特殊的内置方法没有在实例上查找。它们是在矩阵类型上查找的。"确实,感谢您为我指明了正确的方向!我现在已经完成了基本操作,我将把它作为答案发布
【解决方案2】:

如果有人对溢出问题感兴趣,并且相信 jdi 和 kindall 的专业知识,那么操作符似乎必须是类方法 - 因此,动态方法生成需要一个自定义类。 '我得到了以下工作原型(对于 +=、-=、*=./=)

class OverflowPreventer( object ):
    '''A context manager that exposes a numpy array preventing simple operations from overflowing.
    Example:
    array= numpy.array( [255], dtype=numpy.uint8 )
    with OverflowPreventer( array ) as prevented:
        prevented+=1
    print array'''
    inverse_operator= { '__iadd__':'__sub__', '__isub__':'__add__', '__imul__': '__div__', '__idiv__':'__mul__'}
    bypass_operators=['__str__', '__repr__', '__getitem__']
    def __init__( self, matrix ):
        class CustomWrapper( object ):
            def __init__(self, matrix):
                assert matrix.dtype==numpy.uint8
                self.overflow_matrix= matrix
                self.overflow_lower_range= float(0)
                self.overflow_upper_range= float(2**8-1)
                for op in OverflowPreventer.bypass_operators:
                    setattr(CustomWrapper, op, getattr(self.overflow_matrix, op))

            def _overflow_operator( self, b, forward_operator):
                m, lr, ur= self.overflow_matrix, self.overflow_lower_range, self.overflow_upper_range
                assert type(b) in (int, float)
                reverse_operator= OverflowPreventer.inverse_operator[forward_operator]
                uro= getattr( ur, reverse_operator)
                lro= getattr( lr, reverse_operator)
                afo= getattr( m, forward_operator )
                overflows= m > uro( b )
                underflows= m < lro( b )
                afo( b )
                m[overflows]= ur
                m[underflows]= lr
                return self

            def __getattr__(self, attr):
                if hasattr(self.wrapped, attr):
                    return getattr(self.wrapped,attr)
                else:
                    raise AttributeError

        self.wrapper= CustomWrapper(matrix)
        import functools
        for op in OverflowPreventer.inverse_operator.keys():
            setattr( CustomWrapper, op, functools.partial(self.wrapper._overflow_operator, forward_operator=op))

    def __enter__( self ):
        return self.wrapper

    def __exit__( self, type, value, tb ):
        pass

【讨论】:

    猜你喜欢
    • 2012-06-17
    • 2019-06-19
    • 2021-02-28
    • 1970-01-01
    • 2016-11-12
    • 2015-05-11
    • 2019-06-20
    • 2021-04-13
    • 1970-01-01
    相关资源
    最近更新 更多