Путаница по поводу итераторов Python - PullRequest
0 голосов
/ 13 ноября 2018

Чем больше я практикую итераторы, тем больше я запутываюсь.Я чувствую себя довольно уверенно в объектах и ​​классах (единственное, что мы узнали, а не изученное наследование), но итераторы и генераторы теряют голову.Любая помощь высоко ценится.

У меня есть несколько вопросов:

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», а другой возвращается сам.каков реальный тип данных себя?это тип main () или итератор?
  2. Похоже на приведенный выше вопрос - когда мы даем do next (self), какое self мы даем next (iterator или oftype (a))?
  3. Если self после возврата __iter__ (также используется next), имеет итератор типа, как может быть доступ 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 голосов
/ 13 ноября 2018

Это отвечает только на ваш первый вопрос и может помочь вам с вопросом 2.

Цитата из «Свободного питона» (стр. 420):

[...]Объекты, реализующие метод __iter__, возвращающий итератор, являются итеративными.[...]

Это означает, что вы могли бы (теоретически) сделать что-то вроде этого:

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

    def __iter__(self):
        return MainIterator(self)

Теперь, но как выглядит класс MainIterator?Итератору просто нужен метод __next__ dunder, чтобы определить следующее возвращаемое значение.Реализация может выглядеть так:

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]

В основном я делаю создание ссылки на вызываемый итерируемый объект и сохранение его в self.iterable.Теперь каждый раз, когда вызывается метод dunder __next__, он возвращает элемент массива, пока итератор не будет исчерпан.На это указывает повышение StopIteration.

Вы не видите такой реализации очень часто, так как эти два класса часто объединяются в один класс.Я просто хотел продемонстрировать, что можно разделить их.Результатом является то, что @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]

Разница в том, что __init__ возвращает сам экземпляр, так как сам класс теперь итеративный и итератор (помните: итератор имеет dunder __next__)итератор имеет метод __iter__ dunder, который возвращает итератор).

Последний интересный момент - при вызове этих методов dunder.На самом деле, при использовании синтаксиса for in это синтаксический сахар для:

a = Main()

## recreating the for in loop

itr = a.__iter__()

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

Сначала инициализируется итератор, а __next__ возвращает значение, пока итератор не будет исчерпан.

РЕДАКТИРОВАТЬ:

Вы действительно должны прочитать мой пост снова.Не рекомендуется разделять итератор.Это просто чтобы продемонстрировать, как они работают внутри.Также, пожалуйста, не определяйте свои собственные методы.Это сломает ваш код в какое-то время.Я исправил ваш класс 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)

Вывод на моем компьютере:

up
above
down
below
0 голосов
/ 13 ноября 2018

Вот что я придумал:

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)
0 голосов
/ 13 ноября 2018

Я немного переписал ваш код с большим количеством комментариев, чтобы попытаться объяснить, что происходит в примере (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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...