que - Clase de Python accesible por iterador e índice
python funciones generadoras (2)
Podría ser una pregunta n00b, pero actualmente tengo una clase que implementa un iterador para que pueda hacer algo como
for i in class():
pero quiero poder acceder a la clase por índice y también me gusta
class()[1]
¿Cómo puedo hacer eso?
¡Gracias!
Implementar los __iter__()
y __getitem__()
et alia.
La respuesta aceptada actual de @ Ignacio Vázquez-Abrams es suficiente. Sin embargo, otros interesados en esta pregunta pueden considerar heredar su clase de una clase base abstracta (como las que se encuentran en las collections.abc
módulos estándar.abc ). Esto hace una serie de cosas ( probablemente también haya otras ):
- asegura que todos los métodos que necesita para tratar su objeto "como un ____" están ahí
- es autodocumentado, en el sentido de que alguien que lea su código pueda saber instantáneamente que tiene la intención de que su objeto "actúe como un ____".
- permite que
isinstance(myobject,SomeABC)
funcione correctamente. - a menudo proporciona métodos auto-mágicamente por lo que no tenemos que definirlos nosotros mismos
(Tenga en cuenta que, además de lo anterior, crear su propio ABC puede permitirle probar la presencia de un método específico o un conjunto de métodos en cualquier objeto, y en base a esto declarar que ese objeto es una subclase del ABC, incluso si el objeto no hereda directamente del ABC . Consulte esta respuesta para obtener más información ) .
Ahora, como ejemplo, vamos a elegir e implementar un ABC para la clase en la pregunta original. Hay dos requisitos:
- la clase es iterable
- acceder a la clase por índice
Obviamente, esta clase va a ser una especie de colección. Entonces, lo que haremos es mirar nuestro menú de collection
ABC para encontrar el ABC apropiado (tenga en cuenta que también hay ABC numéricos ). El ABC apropiado depende de qué métodos abstractos deseamos usar en nuestra clase.
Vemos que lo que buscamos es un Iterable
si queremos usar el método __iter__()
, que es lo que necesitamos para hacer cosas como for o in myobject:
Sin embargo, un Iterable
no incluye el método __getitem__()
, que es lo que necesitamos para hacer cosas como myobject[i]
. Entonces, tendremos que usar un ABC diferente.
En el menú collections.abc
de las clases base abstractas, vemos que una Sequence
es el ABC más simple para ofrecer la funcionalidad que necesitamos. Y, ¿lo verían? Tenemos la funcionalidad Iterable
como método de mezcla, lo que significa que no tenemos que definirlo nosotros mismos, ¡gratis! También obtenemos __contains__
, __reversed__
, index
y count
. Lo cual, si lo piensas, son todas las cosas que deberían incluirse en cualquier objeto indexado. Si se olvidó de incluirlos, los usuarios de su código (¡incluido, potencialmente, usted mismo!) Podrían sentirse bastante molestos (sé que lo haría).
Sin embargo, hay un segundo ABC que también ofrece esta combinación de funcionalidad (iterable y accesible por []
): un Mapping
. ¿Cuál queremos usar?
Recordamos que el requisito es poder acceder al objeto por índice (como una list
o una tuple
), es decir, no por clave (como un dict
). Por lo tanto, seleccionamos Sequence
lugar de Mapping
.
Es importante tener en cuenta que una Sequence
es de solo lectura (como lo es un Mapping
), por lo que no nos permitirá hacer cosas como myobject[i] = value
, o random.shuffle(myobject)
. Si queremos poder hacer cosas como esa, tenemos que continuar hacia abajo en el menú de ABC y usar una MutableMapping
(o una MutableMapping
).
Ahora podemos hacer nuestra clase. Lo definimos y lo heredamos de Sequence
.
from collections.abc import Sequence
class MyClass(Sequence):
pass
Si intentamos usarlo, el intérprete nos dirá qué métodos debemos implementar antes de que se pueda usar (tenga en cuenta que los métodos también se enumeran en la página de documentación de Python):
>>> myobject = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can''t instantiate abstract class MyClass with abstract methods __getitem__, __len__
Esto nos dice que si continuamos e implementamos __getitem__
y __len__
, podremos usar nuestra nueva clase. Podríamos hacerlo así en Python 3:
from collections.abc import Sequence
class MyClass(Sequence):
def __init__(self,L):
self.L = L
super().__init__()
def __getitem__(self, i):
return self.L[i]
def __len__(self):
return len(self.L)
# Let''s test it:
myobject = MyClass([1,2,3])
try:
for idx,_ in enumerate(myobject):
print(myobject[idx])
except Exception:
print("Gah! No good!")
raise
# No Errors!
¡Funciona!