关于python迭代器的困惑

时间:2018-11-13 11:29:30

标签: python python-3.x python-2.7 iterator iterable

我练习迭代器的次数越多,我就越困惑。我对对象和类很有信心(只有我们学到的知识,而没有学到继承),但是迭代器和生成器使我很困惑。非常感谢您的帮助。

我有一些问题:

1)在下面的代码中:

class main():
    def __init__(self):
        self.items=[1,2,3,4,5,6,7]
        self.index= 0

    def __iter__(self):
        return self 

    def __next__(self):
        self.index+=1

        return self.items[self.index]

a = main()

for i in a:
    print(i)  
  1. 我们这里有两个自我。一个在init中,它指向对象“ a”,而另一个则由self返回。自我的真实数据类型是什么?是main()类型还是迭代器?
  2. 与上述问题类似-当我们给予 next (自我)时,我们给予下一个(迭代器或类型(a)的自我)是什么?
  3. 如果__iter__返回后的self(也供next使用),则为iterator类型,如何访问self.index

2)在下面的代码中,我试图遍历特定的事物,例如键,值或字典类中的项。 它抛出错误“迭代器”对象没有属性“索引”。 为什么self.index无法访问字典类的实例变量索引?

class Pair():
    def __init__(self, key ,value):
        self.key = key
        self.value = value

class Dictionary():
    def __init__(self):
        self.items =[]
        self.index = -1     ################## INDEX DEFINED HERE

    def __setitem__(self, key, value):
        for i in self.items:
            if i.key == key:
                i.value = value
                return
        self.items.append(Pair(key,value))

    def __keys__(self):
        return iterator(self, 'keys')

    def __values__(self):
        return iterator(self, 'values')

    def __items__(self):
        return iterator(self , 'items')

class iterator():
    def __init__(self, object, typo):
        self.typo = typo

    def __iter__(self):
        return self

    def __next__(self):
        if self.typo == 'keys': 
            self.index +=1  #################### ERROR
            if self.index >= len(self.items):
                raise StopIteration
            return self.items[self.index].keys

        ` # Similarly for keys and items as well`

collins = Dictionary()

collins['google'] = 'pixel'
collins['htc'] = 'M8'
collins['samsung'] = 'S9'


for i in collins.__keys__():
    print(i)

3 个答案:

答案 0 :(得分:1)

我用很多注释重写了您的代码,以尝试解释示例(1)中发生的情况。

class MainClass():
    def __init__(self):
        # The value 'self' always refers to the object we are currently working
        # on. In this case, we are instantiating a new object of class
        # MainClass, so self refers to that new object.
        # self.items is an instance variable called items within the object
        # referred to as self.
        self.items = [1, 2, 3, 4, 5, 6, 7]
        # We do not want to declare self.index here. This is a slightly subtle
        # point. If we declare index here, then it will only be set when we first
        # create an object of class MainClass. We actually want self.index to be
        # set to zero each time we iterate over the object, so we should set it
        # to zero in the __iter__(self) method.
        # self.index = 0

    def __iter__(self):
        # This is an instance method, which operates on the current instance of
        # MainClass (an object of class MainClass). This method is called when
        # we start iteration on an object, so as stated above, we'll set
        # self.index to zero.
        self.index = 0
        return self

    def __next__(self):
        # This is also an instance method, which operates on the current
        # instance of MainClass.
        if self.index < len(self.items):
            self.index += 1
            return self.items[self.index - 1]
        else:
            # This is how we know when to stop iterating.
            raise StopIteration()


a = MainClass()

# a is now an object of class MainClass
# Because we have implemented __iter__ and __next__ methods in MainClass,
# objects of class MainClass are iterable, so a is also iterable.

# When we say "for i in a" this is like shorthand for  saying "a.__iter__()"
# and then "i = a.__next__()" until we raise
# a StopIterationException

# Here we are iterating over the result of a.__iter__() until a.__next__()
# raises a StopIterationException
for i in a:
    # Here we are printing the value returned by a.__next__()
    print(i)

我认为在继续进行(2)并仔细检查您对对象和类的了解之前,可能需要先回顾一下。我们在(2)中看到的第一个问题是,您将object传递给了iterator类,但是没有将其存储在任何地方,因此以后无法访问它。但是,当您更全面地了解(1)中的要求后,您可能会发现还有其他更改方式。

答案 1 :(得分:1)

This answers only your first question, and might help you with question 2.

Citing from 'Fluent Python' (p. 420):

[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]

That means, you could (in theory) do something like this:

class Main:
    def __init__(self):
        self.items = list(range(1, 8))
        self.length = len(self.items)

    def __iter__(self):
        return MainIterator(self)

Now, but how does the MainIterator class look like? The iterator just needs a __next__ dunder method to determine the next value it returns. An implementation could look like this:

class MainIterator:
    def __init__(self, iterable):
        self.iterable = iterable
        self.index = 0

    def __next__(self):
        if self.index >= self.iterable.length:
            raise StopIteration

        self.index += 1
        return self.iterable.items[self.index - 1]

What I am basically doing is creating a reference to the calling iterable and saving it in self.iterable. Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. This is indicated by raising StopIteration.

You do not see such an implementation very often, as these two classes are often merged into a single class. I just wanted to demonstrate that it is possible to separate the two. The result is what @rbricheno already posted:

class Main:
    def __init__(self):
        self.items = list(range(1, 8))
        self.length = len(self.items)

    def __iter__(self):
        self.index = 0
        return self

    def __next__(self):
        if self.index >= self.length:
            raise StopIteration

        self.index += 1
        return self.items[self.index - 1]

The difference is that __init__ returns the instance itself, as the class itself is now iterable and iterator (remember: an iterator has the __next__ dunder method, and an iterable has a __iter__ dunder method that returns an iterator).

The last interesting bit is, when these dunder methods are called. Actually, when using the for in syntax, it is syntactic sugar for:

a = Main()

## recreating the for in loop

itr = a.__iter__()

while True:
    try:
        print(itr.__next__())
    except StopIteration:
        break

You initialize the iterator first, and __next__ returns a value until the iterator is exhausted.

EDIT:

You should really read my post again. It is NOT good practice to separate the iterator. It's just to demonstrate how they work internally. Also, please do not define your own dunder methods. That will break your code at some time. I have corrected your dict class below, but I iterate over the pair, not its components.

class Pair:

    def __init__(self, key, value):
        self.key = key
        self.value = value

    ## you need this to display your class in a meaningful way
    def __repr__(self):
        return f'{__class__.__name__}({self.key}, {self.value})'

class Dictionary:

    def __init__(self):
        self.items = []
        self.length = len(self.items)

    def add(self, objects):
        self.items.append(objects)
        self.length += 1

    def __iter__(self):
        self.index = 0
        return self

    def __next__(self):
        if self.index >= self.length:
            raise StopIteration

        self.index += 1
        return self.items[self.index - 1]

a = Dictionary()

a.add(Pair('up', 'above'))
a.add(Pair('down', 'below'))

for i in a:
    print(i.key)
    print(i.value)

Output on my machine:

up
above
down
below

答案 2 :(得分:0)

这就是我想出的:

class Pair():
    def __init__(self, key, value):
        self.key = key
        self.value = value


class dictionary():
    def __init__(self):
        self.items = []

    def add(self, objects):
        self.items.append(objects)

    def __keys__(self):
        return iterator(self, 'keys')

    def __values__(self):
        return iterator(self, 'values')

class iterator():
    def __init__(self, to_be_iterated , over_what):
        self.to_be_iterated = to_be_iterated
        self.over_what = over_what


    def __iter__(self):
        self.index = -1
        return self

    def __next__(self):
        self.index += 1
        if self.over_what == 'keys':
            try:
                    return self.to_be_iterated.items[self.index].key
            except Exception:
                raise StopIteration

        elif self.over_what == 'values':
            try:
                    return self.to_be_iterated.items[self.index].value
            except Exception:
                raise StopIteration


collins = dictionary()

collins.add(Pair('up', 'above'))
collins.add(Pair('down', 'below'))

for i in collins.__keys__():
    print(i)

for i in collins.__values__():
    print(i)