分配(而不是定义)一个 __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
我有一个包装器 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