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

时间:2013-12-06 18:05:20

标签: python

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

  1. 拥有相同的界面int并且正确使用int
  2. 使用U32类(int + U32U32 + int等)的任何操作也会返回U32
  3. 是纯粹的蟒蛇 - 我不想使用NumPy,ctypes等。
  4. 可以在this answer中找到,我得到了一个在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 个答案:

答案 0 :(得分:6)

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

class U32(object):

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

在实践中,这意味着直接在__div__本身上查找U32等方法,而不是U32实例上的属性,以及{{ 1}}不咨询钩子。

不幸的是,特殊方法查找还绕过任何__getattr____getattr__个钩子。请参阅documentation on Special Method lookups

  

除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过__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_) 时自动强制返回自定义类。

答案 1 :(得分:0)

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

(已通过Python 3.7测试)

indicator="ind_c"

现在可以轻松继承其他uint类型:

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

输出

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'>

我创建了这些算法,以接管现有加密算法的一些计算,这些算法很大程度上依赖于uint32上的移位。

一个值得注意的选择是反射(或交换)运算符功能。这些是带有r前缀的字符,例如__radd __,__ rsub__等。它们在我们的uint类型是第二个运算符时才起作用,例如1 + U8(0xff)。如果实现了__radd__,则结果将为0x0且类型为U8;如果未实现__radd__,则结果将为0x100且类型为int。虽然U8(0xff)+1 = 0保持不变(因为已定义__add__)。 对于我的用例,最好同时定义反射函数,以确保所有结果均为U32类型。