python - Asignar(en lugar de definir) un método mágico__getitem__ rompe la indexación
class indexing (2)
Deben definirse métodos especiales (esencialmente cualquier cosa con dos guiones bajos en cada extremo) en la clase. El procedimiento de búsqueda interna para métodos especiales omite por completo el dict de instancia. Entre otras cosas, esto es así si lo haces
class Foo(object):
def __repr__(self):
return ''Foo()''
__repr__
método
__repr__
que definió solo se usa para instancias de
Foo
, y no para
repr(Foo)
.
Esta pregunta ya tiene una respuesta aquí:
Tengo una clase de contenedor similar a este ejemplo (muy simplificado):
class wrap(object):
def __init__(self):
self._data = range(10)
def __getitem__(self, key):
return self._data.__getitem__(key)
Puedo usarlo así:
w = wrap()
print w[2] # yields "2"
Pensé que podía optimizar y deshacerme de una llamada de función cambiando a esto:
class wrap(object):
def __init__(self):
self._data = range(10)
self.__getitem__ = self._data.__getitem__
Sin embargo, recibo un
TypeError: el objeto ''wrap'' no admite indexación
para la línea
print w[2]
con la última versión.
La llamada directa al método, es decir,
print w.__getitem__(2)
, funciona en ambos casos ...
¿Por qué la versión de asignación no permite la indexación?
En realidad, puede resolver esto creando una nueva clase para cada tipo.
Si desea que esto funcione de manera transparente,
__new__
es el lugar para ello.
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
"Solución" inicial:
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
¡Pero ten cuidado!
Esto hará lo que quiera: use la clase envuelta ''
__getitem__
.
Sin embargo, esto no siempre tiene sentido.
Por ejemplo, la
list.__getitem__
está realmente integrada en el CAPI de CPython y no es aplicable a otros tipos.
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