分配(而不是定义)__getitem__魔术方法会破坏索引

时间:2016-06-30 20:55:27

标签: python class indexing wrapper magic-methods

我有一个类似于这个(强烈简化的)示例的包装类:

class wrap(object):
    def __init__(self):
        self._data = range(10)

    def __getitem__(self, key):
        return self._data.__getitem__(key)

我可以这样使用它:

w = wrap()
print w[2] # yields "2"

我认为我可以通过更改为:

来优化和删除一个函数调用
class wrap(object):
    def __init__(self):
        self._data = range(10)
        self.__getitem__ = self._data.__getitem__

但是,我收到了

  

TypeError:'wrap'对象不支持索引

表示后一版本的print w[2]行。

直接调用该方法,即print w.__getitem__(2),在两种情况下都有效......

为什么分配版本不允许编制索引?

2 个答案:

答案 0 :(得分:5)

必须在类上定义特殊方法(基本上每端都有两个下划线)。特殊方法的internal lookup procedure完全跳过实例dict。除此之外,如果你这样做是这样的

class Foo(object):
    def __repr__(self):
        return 'Foo()'

您定义的__repr__方法仅用于Foo的实例,而不用于repr(Foo)

答案 1 :(得分:1)

您实际上可以通过为每种类型创建一个新类来解决这个问题。如果您希望透明地工作,__new__就是它的地方。

import weakref


class BigWrap(object):
    def __new__(cls, wrapped):
        wrapped_type = type(wrapped)
        print('Wrapping %s (%s)' % (wrapped, wrapped_type))
        # creates a new class, aka a new type
        wrapper_class = type(  # new_class = type(class name, base classes, class dict)
            '%s_%s_%d' % (cls.__name__, wrapped_type.__name__, id(wrapped)),  # dynamic class name
            (
                cls,  # inherit from wrap to have all new methods
                wrapped_type,  # inherit from wrap_type to have all its old methods
            ),
            {
                '__getitem__': wrapped.__getitem__,  # overwrite __getitem__ based on wrapped *instance*
                '__new__': wrapped_type.__new__,  # need to use wrapped_type.__new__ as cls.__new__ is this function
            })
        cls._wrappers[wrapped_type] = wrapper_class  # store wrapper for repeated use
        return cls._wrappers[wrapped_type](wrapped)

    # self is already an instance of wrap_<type(wrapped)>
    def __init__(self, wrapped):
        self.__wrapped__ = wrapped

初始“解决方案”:

import weakref
class wrap(object):
  _wrappers = weakref.WeakValueDictionary()  # cache wrapper classes so we don't recreate them

  def __new__(cls, wrapped):
    wrapped_type = type(wrapped)
    print('Wrapping %s (%s)' % (wrapped, wrapped_type))
    try:
      return object.__new__(cls._wrappers[wrapped_type])  # need to use object.__new__ as cls.__new__ is this function
    except KeyError:
      print('Creating Wrapper %s (%s)' % (wrapped, wrapped_type))
    # creates a new class, aka a new type
    wrapper_class = type(  # class name, base classes, class dict
      '%s_%s' % (cls.__name__, wrapped_type.__name__),  # dynamic class name
      (cls,),  # inherit from wrap to have all its method
      {'__getitem__': wrapped_type.__getitem__})  # overwrite __getitem__ based on wrapped class
    cls._wrappers[wrapped_type] = wrapper_class  # store wrapper for repeated use
    return cls._wrappers[wrapped_type](wrapped)

  # self is already an instance of wrap_<type(wrapped)>
  def __init__(self, wrapped):
    self._data = wrapped

但要小心!这将做你想要的 - 使用包装的类'__getitem__。但是,这并不总是有意义的!例如,list.__getitem__实际上内置于CPython的CAPI中,不适用于其他类型。

foo = wrap([1,2,3])
print(type(foo))  # __main__.wrap_list
foo[2]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-82791be7104b> in <module>()
----> 1 foo[2]

TypeError: descriptor '__getitem__' for 'list' objects doesn't apply to 'wrap_list' object