2013-12-06 3 views
5

Мне нужно написать класс, который реализует 32-разрядные целые числа без знака так же, как они работают на языке программирования C. То, что я забочусь о большинстве являются бинарными сдвиги, но я вообще хочу, чтобы мой класс:Python 3 __getattr__ ведет себя иначе, чем в Python 2?

  1. имеют одинаковый интерфейс int имеет и работает с int надлежащим
  2. Любая операция с моим U32 класса (ИНТ U32, U32 + ИНТ и т.д.) также возвращает U32
  3. быть чисто-питона - Я не хочу использовать NumPy, ctypes и т.д.

Как можно найти в 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' 

похоже getattr трюк не дозвонились вообще в Python 3. Почему? Как я могу получить этот код, работающий как под Python 2, так и 3?

ответ

6

Ваше решение Python 2 опиралось на стиль старого стиля поведение. Ваш код Python 2 потерпит неудачу в том же порядке, как Python 3 вы должны были сделать свой класс наследует от object:

class U32(object): 

Это потому, что специальные методы ищутся на типа, а не сам объект, для классы нового стиля. Это изменение поведения зафиксировало несколько угловых случаев со старой моделью.

На практике это означает, что такие методы, как __div__ ищутся непосредственно на U32 себя, а не в качестве атрибутов на экземплярах из U32 и __getattr__ крюка не советовался.

К сожалению, специальные методы поиска также обход любой __getattr__ или __getattribute__ крючки. Смотрите documentation on Special Method lookups:

In addition to bypassing any instance attributes in the interest of correctness, implicit special method lookup generally also bypasses the __getattribute__() method even of the object’s metaclass:

[...]

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

Ваш единственный вариант, то, чтобы установить все специальные методы динамически на вашем классе. Класс декоратор будет делать хорошо здесь:

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 возвращается.

+1

Не могли бы вы показать пример метакласса '__getattr__'? – d33tah

+0

После этого ответ будет завершен, и я буду рад принять его. – d33tah

+0

@ d33tah: Я добирался до него. :-) Я напишу вам конкретную, тем временем, взгляните на этот [мой старый ответ] (http://stackoverflow.com/a/11281744). –

Смежные вопросы