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

d33*_*tah 5 python

我需要编写一个实现32位无符号整数的类,就像它们在C编程语言中一样.我最关心的是二元转换,但我通常希望我的班级:

  1. 具有相同的接口,int具有与工作int正常
  2. 我的U32类(int + U32,U32+ 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))
Run Code Online (Sandbox Code Playgroud)

这是错误:

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'
Run Code Online (Sandbox Code Playgroud)

getattr在Python 3中看起来根本没有调用这个技巧.为什么会这样?如何在Python 2和3下使用此代码?

Mar*_*ers 6

您的Python 2解决方案依赖于旧样式类行为.您的Python 2代码将以与Python 3相同的方式失败,以使您的类继承自object:

class U32(object):
Run Code Online (Sandbox Code Playgroud)

这是因为对于新式类,在类型而不是对象本身上查找特殊方法.此行为更改修复了旧模型的几个极端情况.

在实践中,这意味着类似__div__的方法直接在U32其自身上查找,而不是作为实例的属性U32,并且不查询__getattr__钩子.

不幸的是,特殊方法查找也绕过任何__getattr____getattribute__钩子.请参阅特殊方法查找文档:

除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过__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_)
Run Code Online (Sandbox Code Playgroud)

我更新了代理函数以正确处理多个参数,并且如果int返回则自动强制返回到自定义类.