Build a Basic Python Iterator

前端 未结 10 1068
南旧
南旧 2020-11-21 12:22

How would one create an iterative function (or iterator object) in python?

10条回答
  •  情歌与酒
    2020-11-21 12:52

    There are four ways to build an iterative function:

    • create a generator (uses the yield keyword)
    • use a generator expression (genexp)
    • create an iterator (defines __iter__ and __next__ (or next in Python 2.x))
    • create a class that Python can iterate over on its own (defines __getitem__)

    Examples:

    # generator
    def uc_gen(text):
        for char in text.upper():
            yield char
    
    # generator expression
    def uc_genexp(text):
        return (char for char in text.upper())
    
    # iterator protocol
    class uc_iter():
        def __init__(self, text):
            self.text = text.upper()
            self.index = 0
        def __iter__(self):
            return self
        def __next__(self):
            try:
                result = self.text[self.index]
            except IndexError:
                raise StopIteration
            self.index += 1
            return result
    
    # getitem method
    class uc_getitem():
        def __init__(self, text):
            self.text = text.upper()
        def __getitem__(self, index):
            return self.text[index]
    

    To see all four methods in action:

    for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
        for ch in iterator('abcde'):
            print(ch, end=' ')
        print()
    

    Which results in:

    A B C D E
    A B C D E
    A B C D E
    A B C D E
    

    Note:

    The two generator types (uc_gen and uc_genexp) cannot be reversed(); the plain iterator (uc_iter) would need the __reversed__ magic method (which, according to the docs, must return a new iterator, but returning self works (at least in CPython)); and the getitem iteratable (uc_getitem) must have the __len__ magic method:

        # for uc_iter we add __reversed__ and update __next__
        def __reversed__(self):
            self.index = -1
            return self
        def __next__(self):
            try:
                result = self.text[self.index]
            except IndexError:
                raise StopIteration
            self.index += -1 if self.index < 0 else +1
            return result
    
        # for uc_getitem
        def __len__(self)
            return len(self.text)
    

    To answer Colonel Panic's secondary question about an infinite lazily evaluated iterator, here are those examples, using each of the four methods above:

    # generator
    def even_gen():
        result = 0
        while True:
            yield result
            result += 2
    
    
    # generator expression
    def even_genexp():
        return (num for num in even_gen())  # or even_iter or even_getitem
                                            # not much value under these circumstances
    
    # iterator protocol
    class even_iter():
        def __init__(self):
            self.value = 0
        def __iter__(self):
            return self
        def __next__(self):
            next_value = self.value
            self.value += 2
            return next_value
    
    # getitem method
    class even_getitem():
        def __getitem__(self, index):
            return index * 2
    
    import random
    for iterator in even_gen, even_genexp, even_iter, even_getitem:
        limit = random.randint(15, 30)
        count = 0
        for even in iterator():
            print even,
            count += 1
            if count >= limit:
                break
        print
    

    Which results in (at least for my sample run):

    0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
    0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
    0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
    0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
    

    How to choose which one to use? This is mostly a matter of taste. The two methods I see most often are generators and the iterator protocol, as well as a hybrid (__iter__ returning a generator).

    Generator expressions are useful for replacing list comprehensions (they are lazy and so can save on resources).

    If one needs compatibility with earlier Python 2.x versions use __getitem__.

提交回复
热议问题