2010-11-23 17 views
6

La ejecución de este:¿Cómo sobrescribir el comportamiento de la lista de Python (iterador)?

class DontList(object): 
    def __getitem__(self, key): 
     print 'Getting item %s' % key 
     if key == 10: raise KeyError("You get the idea.") 
     return None 

    def __getattr__(self, name): 
     print 'Getting attr %s' % name 
     return None 

list(DontList()) 

Produce esto:

Getting attr __length_hint__ 
Getting item 0 
Getting item 1 
Getting item 2 
Getting item 3 
Getting item 4 
Getting item 5 
Getting item 6 
Getting item 7 
Getting item 8 
Getting item 9 
Getting item 10 
Traceback (most recent call last): 
    File "list.py", line 11, in <module> 
    list(DontList()) 
    File "list.py", line 4, in __getitem__ 
    if key == 10: raise KeyError("You get the idea.") 
KeyError: 'You get the idea.' 

cómo puedo cambiar eso para que voy a conseguir [], al tiempo que permite el acceso a dichas claves [1] etc.?

(He intentado poner en def __length_hint__(self): return 0, pero no ayuda.)

Mi caso de uso real: (para la lectura si va a ser útil, no dude en ignorar allá de este punto)

Después de aplicar a certain patch a iniparse, he encontrado un desagradable efecto secundario en mi parche. Tener __getattr__ establecido en mi clase Undefined, que devuelve un nuevo objeto Undefined. Desafortunadamente, esto significa que list(iniconfig.invalid_section) (donde isinstance(iniconfig, iniparse.INIConfig)) está haciendo esto (poner en simples print s en el __getattr__ y __getitem__):

Getting attr __length_hint__ 
Getting item 0 
Getting item 1 
Getting item 2 
Getting item 3 
Getting item 4 

Etcétera hasta el infinito.

+0

Tenga en cuenta que algunas de estas respuestas ignorar ciertos aspectos de la lista (x) - será __iter__ primera llamada y luego __len__ en que si lo que existe y luego se extenderá hasta el iterador. Asegúrate de que si __len__ tiene algún efecto secundario (en uno de mis objetos debe correr por el propio iterador para saber cuántos elementos tiene) que se restablece cuando termina. –

Respuesta

7

Si desea anular la iteración a continuación, sólo se define el método __iter__ en su clase

+0

Pensé muy vagamente sobre eso en un punto, pero no terminé intentándolo, ya que no mostraba 'Obteniendo el atributo __iter__'; aparentemente 'hasattr' o lo que sea que use no lo hace de esa manera. ¿Qué recomendarías entonces para producir '[]' - 'def __iter __ (self): return iter ([])' (funciona pero es la manera correcta)? O tal vez quiero que el objeto 'TypeError: 'Undefined' usual no sea iterable' - ¿hay alguna forma de que pueda obtenerlo de esa manera (sin hacer trampa produciendo ese error yo mismo)? –

+0

Bueno, normalmente aparece la tienda del próximo artículo, por lo que los generadores son difíciles de volver al estado anterior. Siempre puedes tener def __iter __ (self): raise TypeError –

+1

Después de pensar esto y la respuesta de Sven termina, creo que quiero anular '__iter__' con' return; yield' en lugar de subir 'IndexError' on' int' en '__getitem__', para hacer que la sección' INDE 'Indefinida' se comporte como una sección definida - cosas como 'para la sección en iniconfig',' para la clave en la sección', 'if section en iniconfig' y 'if key in section' deberían funcionar también. –

3

Sólo levanta IndexError en lugar de KeyError. KeyError es para clases de tipo mapeo (por ejemplo, dict), mientras que IndexError es para secuencias.

Si define el método __getitem__() en su clase, Python generará automáticamente un iterador a partir de él. Y el iterador termina al IndexError - ver PEP234.

+0

+1 ¡Bingo! [...] – katrielalex

+0

Gracias por la información, pero el problema es que "1" es una sección INI perfectamente válida o un nombre de valor. Pero quizás pueda hacer subir IndexError por '1' y aceptar' '1''. Creo que 'def __iter __ (self): return; yield' es más limpio sin embargo. –

1

Reemplace la iteración de su clase implementando un método __iter__(). Señal de iterador cuando terminan al generar una excepción StopIteration, que es parte del protocolo de iterador normal y no se propaga aún más. He aquí una manera de aplicar eso a su clase de ejemplo:

class DontList(object): 
    def __getitem__(self, key): 
     print 'Getting item %s' % key 
     if key == 10: raise KeyError("You get the idea.") 
     return None 

    def __iter__(self): 
     class iterator(object): 
      def __init__(self, obj): 
       self.obj = obj 
       self.index = -1 
      def __iter__(self): 
       return self 
      def next(self): 
       if self.index < 9: 
        self.index += 1 
        return self.obj[self.index] 
       else: 
        raise StopIteration 

     return iterator(self) 

list(DontList()) 
print 'done' 
# Getting item 0 
# Getting item 1 
# ... 
# Getting item 8 
# Getting item 9 
# done 
+1

creo que es una exageración ... si quieres hacer eso (y el OP no lo hace) simplemente levanta StopIteration en lugar de KeyError en __getitem_ – Ant

+0

@Ant: Maybe. Escribí una versión completa porque no estaba seguro de si el OP quería parar en 10 u omitirlo. Hacer que el iterador sea un objeto separado lo hace mucho más general ... y flexible. – martineau

+0

No, eso fue solo una demostración del problema de que no se detiene. En realidad, 'def __iter __ (self): return; yield' funciona bastante bien. –

0

creo que el uso de return iter([]) es el camino correcto, pero vamos a empezar a pensar cómo list() obras:

recuperar un elemento de __iter__; Si recibe un error de StopIrteration stops..then conseguir que el elemento ..

Así que sólo tienes que yield un generador de vacío en __iter__, por ejemplo (x for x in xrange(0, 0)), o simplemente iter([]))

+0

'iter ([])' funciona, pero es demasiado hacky. 'devolver; yield' también es ligeramente hacky en la forma en que abusa del rendimiento pero es más nítido. –

3

como dice @Sven, ese es el error erróneo aumento. Pero ese no es el punto, el punto es que esto está roto porque es , no es algo que deba hacer: evitar que __getattr__ eleve AttributeError significa que ha anulado la metodología predeterminada de Python para probar si un objeto tiene un atributo y lo reemplazó con un nuevo uno (ini_defined(foo.bar)).

¡Pero Python ya tiene hasattr! ¿Por qué no usar eso?

>>> class Foo: 
...  bar = None 
... 
>>> hasattr(Foo, "bar") 
True 
>>> hasattr(Foo, "baz") 
False 
+0

+1. Por lo general, no recomiendo las respuestas de "no hagas eso", pero este es uno de esos casos en los que Python no hace obvio que no deberías hacer esto, y sin embargo, parece que nunca funciona demasiado bien en la práctica. . Mi instinto es, no hagas eso. –

+0

Creo que este cambio API rompería cualquier código existente que use 'hasattr' para probar si existe una clave en una sección indefinida. –

+0

La razón por la que debe hacerse con el objeto Indefinido es para que pueda establecer valores INI sin necesidad de crear explícitamente la sección. Ese es el diseño de iniparse y eso es correcto. 'ini_defined' es simplemente una función simple que escribí para poder verificar si una sección o valor * fue * definido. ''bar 'in foo' también debería funcionar (en realidad, es más nítido, pero no está definido de la misma forma que este negocio de list()) - Creo que cambiaré todo el uso de' ini_defined (foo.bar) 'a ''barra 'en foo'. –

Cuestiones relacionadas