首页 文章

构建一个基本的Python迭代器

提问于
浏览
482

如何在python中创建迭代函数(或迭代器对象)?

9 回答

  • 555

    python中的迭代器对象符合迭代器协议,这基本上意味着它们提供了两种方法: __iter__()next() . __iter__ 返回迭代器对象,并在循环开始时隐式调用 . next() 方法返回下一个值,并在每个循环增量处隐式调用 . 当没有更多值要返回时, next() 会引发一个StopIteration异常,这是由循环结构隐式捕获的,以便停止迭代 .

    这是一个简单的计数器示例:

    class Counter:
        def __init__(self, low, high):
            self.current = low
            self.high = high
    
        def __iter__(self):
            return self
    
        def next(self): # Python 3: def __next__(self)
            if self.current > self.high:
                raise StopIteration
            else:
                self.current += 1
                return self.current - 1
    
    
    for c in Counter(3, 8):
        print c
    

    这将打印:

    3
    4
    5
    6
    7
    8
    

    使用生成器更容易编写,如前面的答案所述:

    def counter(low, high):
        current = low
        while current <= high:
            yield current
            current += 1
    
    for c in counter(3, 8):
        print c
    

    打印输出将是相同的 . 在引擎盖下,生成器对象支持迭代器协议,并执行与类Counter类似的操作 .

    David Mertz的文章Iterators and Simple Generators是一个非常好的介绍 .

  • 99

    有四种方法可以构建迭代函数:

    例子:

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

    要查看所有四种方法:

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

    结果如下:

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

    Note

    两种发电机类型( uc_genuc_genexp )不能是 reversed() ;普通迭代器( uc_iter )需要 __reversed__ 魔术方法(必须返回一个倒退的新迭代器);并且getitem iteratable( uc_getitem )必须具有 __len__ 魔术方法:

    # for uc_iter
        def __reversed__(self):
            return reversed(self.text)
    
        # for uc_getitem
        def __len__(self)
            return len(self.text)
    

    要回答Panic上校关于无限延迟评估迭代器的第二个问题,下面是这些例子,使用上述四种方法中的每一种:

    # 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
    

    结果导致(至少对于我的样本运行):

    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
    
  • 352

    首先,itertools module对于迭代器很有用的各种情况非常有用,但是这里只需要在python中创建一个迭代器:

    产量

    那不是很酷吗? Yield可用于替换函数中的正常 return . 它返回的对象是相同的,但它不是破坏状态和退出,而是为你想要执行下一次迭代时保存状态 . 以下是直接从itertools function list中提取的实例:

    def count(n=0):
         while True:
             yield n
             n += 1
    

    正如函数描述中所述(它是来自itertools模块的 count() 函数......),它产生一个迭代器,它返回以n开头的连续整数 .

    Generator expressions是其他一整套蠕虫(真棒蠕虫!) . 它们可以用来代替List Comprehension以节省内存(列表推导在内存中创建一个列表,如果没有分配给变量就会在使用后被销毁,但生成器表达式可以创建一个生成器对象...这是一种奇特的说法迭代器) . 以下是生成器表达式定义的示例:

    gen = (n for n in xrange(0,11))
    

    这与上面的迭代器定义非常相似,只是整个范围预定在0到10之间 .

    我刚刚找到 xrange() (我以前没见过它感到很惊讶......)并将它添加到上面的例子中 . xrange()range() 的可迭代版本,其优点是不预先构建列表 . 如果你有一个庞大的数据集来迭代并且只有这么多的内存来完成它将是非常有用的 .

  • 2

    我看到有些人在 __iter__ 做了 return self . 我只想注意 __iter__ 本身可以是一个生成器(因此不需要 __next__ 并提高 StopIteration 异常)

    class range:
      def __init__(self,a,b):
        self.a = a
        self.b = b
      def __iter__(self):
        i = self.a
        while i < self.b:
          yield i
          i+=1
    

    当然,这里也可以直接生成一个生成器,但对于更复杂的类,它可能很有用 .

  • 90

    这个问题是关于可迭代的对象,而不是迭代器 . 在Python中,序列也是可迭代的,因此制作可迭代类的一种方法是使其行为类似于序列,即给它 __getitem____len__ 方法 . 我在Python 2和3上测试了这个 .

    class CustomRange:
    
        def __init__(self, low, high):
            self.low = low
            self.high = high
    
        def __getitem__(self, item):
            if item >= len(self):
                raise IndexError("CustomRange index out of range")
            return self.low + item
    
        def __len__(self):
            return self.high - self.low
    
    
    cr = CustomRange(0, 10)
    for i in cr:
        print(i)
    
  • 0

    这是一个没有 yield 的可迭代函数 . 它使用了 iter 函数和一个闭包,它将状态保存在python 2的封闭范围内的可变( list )中 .

    def count(low, high):
        counter = [0]
        def tmp():
            val = low + counter[0]
            if val < high:
                counter[0] += 1
                return val
            return None
        return iter(tmp, None)
    

    对于Python 3,闭包状态在封闭范围内保持不可变, nonlocal 在本地范围内用于更新状态变量 .

    def count(low, high):
        counter = 0
        def tmp():
            nonlocal counter
            val = low + counter
            if val < high:
                counter += 1
                return val
            return None
        return iter(tmp, None)
    

    测试;

    for i in count(1,10):
        print(i)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
  • 3

    如果你寻找简短的东西,也许它对你来说已经足够了:

    class A(object):
        def __init__(self, l):
            self.data = l
    
        def __iter__(self):
            return iter(self.data)
    

    用法示例:

    In [3]: a = A([2,3,4])
    
    In [4]: [i for i in a]
    Out[4]: [2, 3, 4]
    
  • 1

    此页面上的所有答案都非常适合复杂的对象 . 但是对于包含内置可迭代类型作为属性的那些,如 strlistsetdict ,或 collections.Iterable 的任何实现,您可以省略类中的某些内容 .

    class Test(object):
        def __init__(self, string):
            self.string = string
    
        def __iter__(self):
            # since your string is already iterable
            return (ch for ch in string)
    

    它可以像:

    for x in Test("abcde"):
        print(x)
    
    # prints
    # a
    # b
    # c
    # d
    # e
    
  • 8

    受Matt Gregory的启发,这里的回答是一个更复杂的迭代器A,B,...,Z,AA,AB,...,ZZ,AAA,AAB,...,ZZY,ZZZ

    class AlphaCounter:
        def __init__(self, low, high):
            self.current = low
            self.high = high
    
        def __iter__(self):
            return self
    
        def __next__(self): # Python 3: def __next__(self)
            alpha = ' abcdefghijklmnopqrstuvwxyz'
            n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
            n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
            if n_current > n_high:
                raise StopIteration
            else:
                increment = True
                ret = ''
                for x in self.current[::-1]:
                    if 'z' == x:
                        if increment:
                            ret += 'a'
                        else:
                            ret += 'z'
                    else:
                        if increment:
                            ret += alpha[alpha.find(x)+1]
                            increment = False
                        else:
                            ret += x
                if increment:
                    ret += 'a'
                tmp = self.current
                self.current = ret[::-1]
                return tmp
    
    for c in AlphaCounter('a', 'zzz'):
        print(c)
    

相关问题