开发者

How to override Python list(iterator) behaviour?

开发者 https://www.devze.com 2023-01-26 06:43 出处:网络
Running this: class DontList(object): def __getitem__(self, key): print \'Getting item %s\' % key if key == 10: raise KeyError(\"You get the idea.\")

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 prints 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([]))

0

精彩评论

暂无评论...
验证码 换一张
取 消