Различные генераторы, добавленные в один и тот же словарь, возвращают одинаковые значения - PullRequest
0 голосов
/ 22 сентября 2018

Я только что столкнулся со странным поведением Python (3.7.0), которое мне не совсем понятно, и выглядит для меня как ошибка.Я хочу создать словарь с генераторами, но как-то все они возвращают одинаковые значения.Вот пример кода того, о чем я говорю:

import itertools

d = {
"a": [-1, 2],
"b": [1, 2],
"c": [20, 20]
}
g = dict()
g2 = dict()
for letter, values in d.items():
    g[letter] = (values[0] * values[1] * x for x in itertools.count())
    g2[letter] = [values[0] * values[1] * x for x in range(3)]

for i in range(3):
    for l, v in g.items():
        print(v.__next__())
print(g2)

С моей точки зрения, ожидаемый результат будет одинаковым для элементов g2 и g-генераторов, однако я всегда получаю значения от последнего генератора:

0
0
0
400
400
400
800
800
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}

В заключение, я что-то не так делаю?Или это просто стандартное поведение Python?

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

Эта ошибка не связана с генераторами.Это ошибка области видимости.

В объявлении генератора вы используете имя values, хотя генератор не выполняется до после цикл завершен, где values теперь последнийпункт в списке.Вот пример, который воспроизводит вашу ошибку.

for i in [1]:
    g = (i for _ in range(3))

i = 'some new value'

print(next(g)) # 'some new value'

Другими словами values[0] и values[1] не были привязаны к генератору, и если значение за именем values изменится, то генераторы тожевыводы.

Это означает, что вы хотите, чтобы затвор вокруг вашего генератора сохранял значения values[0] и values[1].Вы можете сделать это, определив свой генератор как функцию.

import itertools

# Here is a function taht will return a generator
def gen(a, b):
    for x in itertools.count():
        yield a * b * x

d = {"a": [-1, 2], "b": [1, 2], "c": [20, 20]}

g, g2 = dict(), dict()

for letter, values in d.items():
    g[letter] = gen(values[0], values[1])
    g2[letter] = [values[0] * values[1] * x for x in range(3)]

for i in range(3):
    for l, v in g.items():
        print(next(v))

print(g2)

Фактически, встроенные генераторы редко используются по этой причине.Воспользуйтесь def-yield способом создания генераторов.

Кроме того, не звоните __next__, используйте вместо него встроенный next.

Вывод

0
0
0
-2
2
400
-4
4
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}
0 голосов
/ 22 сентября 2018

Проверьте фиксированную версию вашего кода:

import itertools

def make_generator(values):
    return (values[0] * values[1] * x for x in itertools.count())

d = {
"a": [-1, 2],
"b": [1, 2],
"c": [20, 20]
}
g = dict()
g2 = dict()
for letter, values in d.items():
    g[letter] = make_generator(values)
    g2[letter] = [values[0] * values[1] * x for x in range(3)]

for i in range(3):
    for l, v in g.items():
        print(v.__next__())
print(g2)

Он распечатывает:

0
0
0
-2
2
400
-4
4
800
{'a': [0, -2, -4], 'b': [0, 2, 4], 'c': [0, 400, 800]}

Дело в том, что в вашем коде все генераторы работают с одним и тем же valuesпеременная в локальной области видимости, поэтому все они в конечном итоге используют values от последнего ключа в dict.В моей версии каждый генератор использует правильный values, потому что каждый генератор создается в отдельной области видимости.

Этого не происходит со списками в g2, потому что они сразу оцениваются с правильными valuesв локальной области, и генераторы оцениваются позже, когда values уже был перезаписан.

...