How to override Python list(iterator) behaviour?
Running this:
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())
Produces this:
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.'
How can I change that so that I'll get []
, while still allowing access to those keys [1]
etc.?
(I've tried putting in def __length_hint__(self): return 0
, but it开发者_Go百科 doesn't help.)
My real use case: (for perusal if it'll be useful; feel free to ignore past this point)
After applying a certain patch to iniparse, I've found a nasty side-effect to my patch. Having __getattr__
set on my Undefined
class, which returns a new Undefined
object. Unfortunately, this means that list(iniconfig.invalid_section)
(where isinstance(iniconfig, iniparse.INIConfig)
) is doing this (put in simple print
s in the __getattr__
and __getitem__
):
Getting attr __length_hint__
Getting item 0
Getting item 1
Getting item 2
Getting item 3
Getting item 4
Et cetera ad infinitum.
If you want to override the iteration then just define the __iter__
method in your class
As @Sven says, that's the wrong error to raise. But that's not the point, the point is that this is broken because it's not something you should do: preventing __getattr__
from raising AttributeError
means that you have overridden Python's default methodology for testing whether an object has an attribute and replaced it with a new one (ini_defined(foo.bar)
).
But Python already has hasattr
! Why not use that?
>>> class Foo:
... bar = None
...
>>> hasattr(Foo, "bar")
True
>>> hasattr(Foo, "baz")
False
Just raise IndexError
instead of KeyError
. KeyError
is meant for mapping-like classes (e.g. dict
), while IndexError
is meant for sequences.
If you define the __getitem__()
method on your class, Python will automatically generate an iterator from it. And the iterator terminates upon IndexError
-- see PEP234.
Override how your class is iterated by implementing an __iter__()
method. Iterator signal when they're finished by raising a StopIteration
exception, which is part of the normal iterator protocol and not propagated further. Here's one way of applying that to your example class:
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
I think that using return iter([])
is the right way, but let's start thinking how list()
works:
Get an element from __iter__
; if receive a StopIrteration
error stops..then get that element..
So you have just to yield
an empty generator in __iter__
, for example (x for x in xrange(0, 0))
, or simply iter([]))
精彩评论