Python 3中的__getattr__与Python 2中的行为不同吗?

7
我需要编写一个类来实现32位无符号整数,就像它们在C编程语言中的工作方式一样。我最关心的是二进制移位,但总体而言,我希望我的类能够:
  1. 具有与 int 相同的接口,并且可以正常与 int 一起使用
  2. 任何涉及到我的 U32 类的操作(int + U32U32 + int等)也应该返回 U32
  3. 纯Python - 我不想使用NumPy,ctypes等第三方库。
正如在此答案中所述,我找到了一种在Python 2下有效的解决方案。最近我尝试在Python 3下运行它,并注意到虽然以下测试代码在旧版Python下可以正常运行,但Python 3会抛出错误:
class U32:
    """Emulates 32-bit unsigned int known from C programming language."""

    def __init__(self, num=0, base=None):
        """Creates the U32 object.

        Args:
            num: the integer/string to use as the initial state
            base: the base of the integer use if the num given was a string
        """
        if base is None:
            self.int_ = int(num) % 2**32
        else:
            self.int_ = int(num, base) % 2**32

    def __coerce__(self, ignored):
        return None

    def __str__(self):
        return "<U32 instance at 0x%x, int=%d>" % (id(self), self.int_)

    def __getattr__(self, attribute_name):
        print("getattr called, attribute_name=%s" % attribute_name)
        # you might want to take a look here:
        # https://stackoverflow.com/q/19611001/1091116
        r = getattr(self.int_, attribute_name)
        if callable(r):  # return a wrapper if integer's function was requested
            def f(*args, **kwargs):
                if args and isinstance(args[0], U32):
                    args = (args[0].int_, ) + args[1:]
                ret = r(*args, **kwargs)
                if ret is NotImplemented:
                    return ret
                if attribute_name in ['__str__', '__repr__', '__index__']:
                    return ret
                ret %= 2**32
                return U32(ret)
            return f
        return r

print(U32(4) / 2)
print(4 / U32(2))
print(U32(4) / U32(2))

这里是错误信息:

Traceback (most recent call last):
  File "u32.py", line 41, in <module>
    print(U32(4) / 2)
TypeError: unsupported operand type(s) for /: 'U32' and 'int'

看起来在Python 3中根本没有调用getattr技巧。为什么会这样?我如何使这个代码在Python 2和3下都能正常工作?

2个回答

10
你的Python 2解决方案依赖于旧式类的行为。如果你让你的类继承自object,那么你的Python 2代码将以与Python 3相同的方式失败。
class U32(object):

这是因为针对新式类,特殊方法是在类型上查找,而不是在对象本身上查找。此行为变更修复了旧模型中的一些边角情况。
实际上,这意味着像 __div__ 这样的方法直接在 U32 上查找,而不是作为 U32 实例的属性查找,并且不会使用 __getattr__ 钩子。
不幸的是,特殊方法查找也会绕过任何 __getattr__ 或 __getattribute__ 钩子。请参阅Special Method lookups 文档
除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过对象元类的__getattribute__()方法:
这种方式绕过__getattribute__()机制为解释器内提供了重大的速度优化空间,但在处理特殊方法时牺牲了一些灵活性(必须将特殊方法设置在类对象本身上,以便解释器能够始终调用它)。
因此,您唯一的选择是在类上动态设置所有特殊方法。在这里,类装饰器就可以胜任。
def _build_delegate(name, attr, cls, type_):
    def f(*args, **kwargs):
        args = tuple(a if not isinstance(a, cls) else a.int_ for a in args)
        ret = attr(*args, **kwargs)
        if not isinstance(ret, type_) or name == '__hash__':
            return ret
        return cls(ret)
    return f

def delegated_special_methods(type_):
    def decorator(cls):
        for name, value in vars(type_).items():
            if (name[:2], name[-2:]) != ('__', '__') or not callable(value):
                continue
            if hasattr(cls, name) and not name in ('__repr__', '__hash__'):
                continue
            setattr(cls, name, _build_delegate(name, value, cls, type_))
        return cls
    return decorator

@delegated_special_methods(int)
class U32(object):
    def __init__(self, num=0, base=None):
        """Creates the U32 object.

        Args:
            num: the integer/string to use as the initial state
            base: the base of the integer use if the num given was a string
        """
        if base is None:
            self.int_ = int(num) % 2**32
        else:
            self.int_ = int(num, base) % 2**32
    def __coerce__(self, ignored):
        return None
    def __str__(self):
        return "<U32 instance at 0x%x, int=%d>" % (id(self), self.int_)

我更新了代理函数以正确处理多个参数,并在返回int时自动强制转换回您的自定义类。


之后,答案就会完整了,我很乐意接受它。 - d33tah
@d33tah:我马上就要做了:-)。同时,你可以先看看我的这个旧回答 - Martijn Pieters
谢谢您提供链接和花费时间。期待着看到具体的内容 :) - d33tah
@d33tah:实际上,我忘记了__special__特殊方法查找也会忽略__getattribute____getattr__。更新后给你一个替代方案。 - Martijn Pieters
@d33tah:不,这只是意味着之前的答案不够完整。现在已经更新以正确处理多个参数,并简化了返回处理过程。 - Martijn Pieters
显示剩余3条评论

0

继承自int并替换您想要使用的所有运算符:

(已在Python 3.7中测试)

class U32(int):
    MAXVALUE = 0xffffffff

    def __new__(cls, value):
        return int.__new__(cls, value & cls.MAXVALUE)

    def __add__(self, *args, **kwargs):
        return self.__new__(type(self),int.__add__(self, *args, **kwargs))

    def __radd__(self, *args, **kwargs):
        return self.__new__(type(self),int.__radd__(self, *args, **kwargs))

    def __sub__(self, *args, **kwargs):
        return self.__new__(type(self), int.__sub__(self, *args, **kwargs))

    def __rsub__(self,*args, **kwargs):
        return self.__new__(type(self),int.__rsub__(self, *args, **kwargs))

    def __mul__(self, *args, **kwargs):
        return self.__new__(type(self),int.__mul__(self, *args, **kwargs))

    def __rmul__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rmul__(self, *args, **kwargs))

    def __div__(self, *args, **kwargs):
        return self.__new__(type(self),int.__floordiv__(self, *args, **kwargs))

    def __rdiv__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rfloordiv__(self, *args, **kwargs))

    def __truediv__(self, *args, **kwargs):
        return self.__new__(type(self),int.__floordiv__(self, *args, **kwargs))

    def __rtruediv__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rfloordiv__(self, *args, **kwargs))

    def __pow__(self, *args, **kwargs):
        return self.__new__(type(self),int.__pow__(self, *args, **kwargs))

    def __rpow__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rpow__(self, *args, **kwargs))

    def __lshift__(self, *args, **kwargs):
        return self.__new__(type(self),int.__lshift__(self, *args, **kwargs))

    def __rlshift__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rlshift__(self, *args, **kwargs))

    def __rshift__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rshift__(self, *args, **kwargs))

    def __rrshift__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rrshift__(self, *args, **kwargs))

    def __and__(self, *args, **kwargs):
        return self.__new__(type(self),int.__and__(self, *args, **kwargs))

    def __rand__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rand__(self, *args, **kwargs))

    def __or__(self, *args, **kwargs):
        return self.__new__(type(self),int.__ror__(self, *args, **kwargs))

    def __ror__(self, *args, **kwargs):
        return self.__new__(type(self),int.__ror__(self, *args, **kwargs))

    def __xor__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rxor__(self, *args, **kwargs))

    def __rxor__(self, *args, **kwargs):
        return self.__new__(type(self),int.__rxor__(self, *args, **kwargs))

现在继承其他 uint 类型变得更加容易:

class U16(U32):
    def __new__(cls, value):
        cls.MAXVALUE = 0xFFFF
        return int.__new__(cls, value&cls.MAXVALUE)


class U8(U32):
    def __new__(cls, value):
        cls.MAXVALUE = 0xFF
        return int.__new__(cls, value&cls.MAXVALUE)

输出

type( U8(0) + 1 ) = <class '__main__.U8'>
U8(0xcde) = 0xde
U8(2) + 0xff = 0x1
U8(0) + 0xfff = 0xff
U8(0) - 2 = 0xfe
U8(0xf) * 32 = 0xe0
U8(0x7)**3 = 0x57
U8(8) / 3 = 0x2
U8(0xff)>>4 = 0xf
U8(0xff)<<4 = 0xf0
type( 1 + U8(0) ) = <class '__main__.U8'>

示例打印机以获取输出示例

def exampleprinter(vec):
    for v in vec:
        result = eval(v)
        if issubclass(type(result), int):
            result = hex(result)
        print(v,'=' , result)

examples = [
            # results are of type U8
            'type( U8(0) + 1 )',
            # Correct tranform on over / underflow
            'U8(0xcde)',
            'U8(2) + 0xff',
            'U8(0) + 0xfff',
            'U8(0) - 2',
            'U8(0xf) * 32',
            'U8(0x7)**3',  # 7 ** 3 = 0x157 -> 0x157 & 0xff = 0x57
            # division will floor any remainder
            'U8(8) / 3',
            # Shifts
            'U8(0xff)>>4',
            'U8(0xff)<<4',
            # when swap operations are defined (the 'r...' ones) returns also U8 on reverse use
            'type( 1 + U8(0) )',
            ]

exampleprinter(examples)

我创建了这些函数来接管一些现有的加密算法计算,这些算法在uint32上依赖于位移操作。

一个显著的选项是反射(或交换)运算符函数。它们是带有r前缀的函数,如__radd__,__rsub__等。当我们的uint类型是第二个运算符时,例如1 + U8(0xff),它们就会发挥作用。如果实现了__radd__,结果将为0x0且类型为U8,如果未实现,则结果为0x100且类型为int。而U8(0xff)+1=0仍然保持不变(因为定义了__add__)。 对于我的用例,最好也定义反射函数以确保所有结果都是U32类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接