我练习迭代器的次数越多,我就越困惑。我对对象和类很有信心(只有我们学到的知识,而没有学到继承),但是迭代器和生成器使我很困惑。非常感谢您的帮助。
我有一些问题:
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)
__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)
答案 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)