Почему в этом коде изменена переменная a? - PullRequest
0 голосов
/ 09 июля 2020
class PowTwo:
    """Class to implement an iterator
    of powers of two"""

    def __init__(self, max=0):
        self.max = max

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

    def __next__(self):
        if self.n <= self.max:
            result = 2 ** self.n
            self.n += 1
            return result
        else:
            raise StopIteration
a = PowTwo(3)
b = iter(a)
print(next(a))

Без этого фрагмента b = iter(a) результат будет

Traceback (most recent call last):
  File "/Users/Mark/test2.py", line 20, in <module>
    print(next(a))
  File "/Users/Mark/test2.py", line 13, in __next__
    if self.n <= self.max:
AttributeError: 'PowTwo' object has no attribute 'n'

Мой вопрос:

b = iter(a) Я не использовал a = iter(a). Почему здесь изменена переменная a?

Ответы [ 2 ]

6 голосов
/ 09 июля 2020

Ваш __iter__ является изменчивым: он устанавливает атрибут n объекта на 0. Это только инициализация n в вашем коде. Если __iter__ не вызывается, __next__ не найдет n при его поиске.

Мутативные методы __iter__ - плохая идея. Вы должны выполнить инициализацию в __init__.

1 голос
/ 10 июля 2020

Python объектам можно присвоить несколько имен. Все эти имена будут относиться к одному и тому же фактическому объекту. Например:

a = [1, 2, 3]
b = a

Оба a и b относятся к одному и тому же объекту. Настройка b[0] = 0 также повлияет на a, потому что это тот же объект. Это причиняет много горя новичкам.

Чтобы привязать объект к имени, вы назначаете его. Задания имеют множество форм. Например, def и class присваивают имена объектам функций и классов соответственно. Эти имена не являются специальными, и вы можете повторно связать их по своему желанию:

def a():
    print('Hi')

b = a   # b now refers to a function object
a = 1   # a is now an integer, while b is the original function

Другой тип присваивания происходит, когда вы передаете аргументы функции. Передаваемые вами объекты привязаны к локальным именам в списке аргументов. Локальные имена функций относятся к тем же самым объектам, которые вы передали, а не к копиям:

a = 1
def f(x):
    print(x is a)
f(a)  # True

Возвращаемые значения работают аналогично, но в обратном порядке. Объекту в функции присваивается имя во внешней области видимости.

Теперь предположим, что у вас есть объект a = PowTwo(3). Когда вы вызываете на нем b = iter(a), происходит следующее:

  1. Вызов iter эквивалентен PowTwo.__iter__(a)
  2. Объект, который вы назначили a в глобальная область видимости привязана к аргументу self в __iter__
  3. Объект self в __iter__ привязан к b вызовом возврата

Теперь a и b относятся к одному и тому же объекту. Вызов next(a) или next(b) после этого передаст тот же объект в next, поскольку к нему привязаны оба имени.

Если бы вы присвоили a = iter(a), вы бы повторно связали a себе. Хотя технически это не означает бездействие, это было бы эквивалентно простому вызову iter(a) и отказу от избыточного возвращаемого значения.

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