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

Assigning (instead of defining) a __getitem__ magic method breaks indexing

我有一个包装器 class 类似于这个(高度简化的)示例:

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' object does not support indexing

用于后一版本的 print w[2] 行。

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

为什么赋值版本不允许索引?

必须在 class 上定义特殊方法(基本上任何两端都有两个下划线的方法)。特殊方法的 internal lookup procedure 完全跳过实例字典。除其他事项外,如果您这样做

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

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

您实际上可以通过为每种类型创建一个新的 class 来解决这个问题。如果你想让它透明地工作,__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

初始"solution":

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

但是要小心!这将做你想做的事——使用包装好的 class' __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