简体   繁体   English

关于python迭代器的困惑

[英]Confusions about python Iterators

The more I practice Iterators, the more I get confused. 我练习迭代器的次数越多,我就越会感到困惑。 I feel pretty confident in Objects and Classes (Only thing we have learned, Not learned inheritance) but Iterators and generators mess my head around. 我对对象和类很有信心(只有我们学到的知识,而没有学到继承),但是迭代器和生成器使我很困惑。 Any help is highly appreciated. 非常感谢您的帮助。

I have a some questions: 我有一些问题:

1) In the code below: 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. We have two self here. 我们在这里有两个自我。 One is in the init which is referring to the object 'a' and another one is returned by self. 一个在init中,它指向对象“ a”,而另一个则由self返回。 what is the real data type of self? 自我的真实数据类型是什么? is it of type main() or it is iterator? 是main()类型还是迭代器?
  2. Similar to the above question - when we give do next (self), what self are we giving to next (iterator or of type(a)) ? 与上述问题类似-当我们给予next (自我)时,我们给予next(iterator或type(a)类型)的自我是什么?
  3. If self after returned by __iter__ (also being used by next), is of the type iterator , how can be access self.index ? 如果由__iter__返回的self之后(也由next使用)为iterator类型,那么如何访问self.index

2) In the code below I am trying to make iterate over specific thing such as keys or values or items in dictionary class. 2)在下面的代码中,我试图遍历特定的事物,例如键或值或字典类中的项。 It is throwing an error ''iterator' object has no attribute 'index'. 它抛出错误“迭代器”对象没有属性“索引”。 Why cant self.index access the instance variable index of dictionary class? 为什么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)

I have rewritten your code a bit with lots of comments to try and explain what is happening in example (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)

I think it might help you to review this before you move on to (2) and double-check what you know about objects and classes. 我认为这可能有助于您在继续进行(2)并仔细检查有关对象和类的知识之前先复习一下。 The first problem we can see in (2) is that you pass an object to your iterator class, but don't store it anywhere so you have no way to access it later. 我们可以在(2)中看到的第一个问题是,您将一个object传递给了iterator类,但是没有将其存储在任何地方,因此您以后无法访问它。 But you may find you have other ways you want to change it when you more fully understand all you have asked about in (1). 但是,当您更全面地了解(1)中的要求时,您可能会发现还有其他更改方式。

This answers only your first question, and might help you with question 2. 这只会回答您的第一个问题,并可能会帮助您解决问题2。

Citing from 'Fluent Python' (p. 420): 引用“ Fluent Python”(第420页):

[...] Objects implementing an __iter__ method returning an iterator are iterable. [...]实现__iter__方法并返回迭代器的对象是可迭代的。 [...] [...]

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? 现在,但是MainIterator类的外观如何? The iterator just needs a __next__ dunder method to determine the next value it returns. 迭代器只需要一个__next__ dunder方法即可确定它返回的下一个值。 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 . 我基本上要做的是创建对可迭代调用的引用,并将其保存在self.iterable Now every time the __next__ dunder method is called, it returns an element of the array, until the iterator is exhausted. 现在,每次调用__next__ dunder方法时,它都会返回数组的元素,直到迭代器耗尽为止。 This is indicated by raising StopIteration . 这通过提高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: 结果是@rbricheno已经发布的内容:

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). 区别在于__init__返回实例本身,因为该类本身现在是可迭代的并且是迭代器(请记住:迭代器具有__next__ dunder方法,而可迭代的具有__iter__ dunder方法可返回迭代器)。

The last interesting bit is, when these dunder methods are called. 最后一个有趣的地方是,当调用这些dunder方法时。 Actually, when using the for in syntax, it is syntactic sugar for: 实际上,在使用for in语法时,它是以下语法糖:

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. 您首先初始化迭代器,然后__next__返回一个值,直到迭代器用尽。

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. 另外,请不要定义自己的dunder方法。 That will break your code at some time. 那会在某些时候破坏您的代码。 I have corrected your dict class below, but I iterate over the pair, not its components. 我已经在下面更正了您的dict类,但是我遍历了这对,而不是它的组件。

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

Thats what I came up with: 那就是我想出的:

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM