Странное поведение распаковки генератора в Python - PullRequest
0 голосов
/ 02 октября 2018

В настоящее время я пытаюсь лучше познакомиться с итераторами в Python, и я столкнулся с некоторым странным поведением.По сути, я получаю неправильное поведение с пониманием генератора, но правильное поведение с пониманием списка.

Позвольте мне начать с объяснения того, что я пытаюсь сделать, а затем, какое поведение я получаю.
Представьте, что у вас есть словарь итераций, например,

d = {'a': [1, 2, 3], 'b': [4, 5]}

Я хочу иметь список словарей со всеми возможными комбинациями итерируемого.Для первого примера это будет

l = [
        {'a': 1, 'b': 4},
        {'a': 1, 'b': 5},
        {'a': 2, 'b': 4},
        {'a': 2, 'b': 5},
        {'a': 3, 'b': 4},
        {'a': 3, 'b': 5},
    ]   

. Для этого я создал этот генератор:

def dict_value_iterator(d):
    for k, v in d.items():         
        yield ((k, vi) for vi in v)

Идея состояла в том, чтобы запустить следующий код, чтобы получить желаемый результат

def get_all_dicts(d):
    return map(dict, *itertools.product(dict_value_iterator(d)))

Теперь о странном поведении.
Чтобы проверить, действительно ли генератор dict_value_iterator сделал то, на что я надеялся, я запустил следующий код:

for i in dict_value_iterator(d):
    print(list(i))

, который действительно делает то, на что я надеялся, а именно, распечатывает следующее:

[('a', 1), ('a', 2), ('a', 3)]
[('b', 4), ('b', 5)]

Однако, когда я запускаю следующий код

def test_unpacking(*args):
    for a in args:
        print(list(a))
test_unpacking(*dict_value_iterator(d))

, я получаю вывод

[('b', 1), ('b', 2), ('b', 3)]
[('b', 4), ('b', 5)]

Для меня это мало что значит, почему распаковка итератора что-то меняет.

Последнее замечание.
Я нашел это, запустив функцию get_all_dicts для d, что привело к следующему выводу

[{'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}, {'b': 4}, {'b': 5}]

ОднакоКогда я изменяю dict_value_iterator следующим образом

def dict_value_iterator(d):
    for k, v in d.items():         
        yield ((k, vi) for vi in v)

, я получаю этот вывод

[{'a': 1, 'b': 4},
 {'a': 1, 'b': 5},
 {'a': 2, 'b': 4},
 {'a': 2, 'b': 5},
 {'a': 3, 'b': 4},
 {'a': 3, 'b': 5}]

, который я и хочу.

1 Ответ

0 голосов
/ 02 октября 2018

Вот упрощенная версия:

generators = []

for i in [1, 2]:
    generators.append((i for _ in [1]))

print(list(generators[0]))  # [2]

Существует только одна переменная с именем i, и цикл for устанавливает ее повторно.Все генераторы, созданные выражением генератора, ссылаются на один и тот же i и не читают его до тех пор, пока цикл не завершится.

Один из способов исправить это - создать другую область видимости с функцией (как в случае сES5, например):

def dict_value_iterator(d):
    def get_generator(k, v):
        return ((k, vi) for vi in v)

    for k, v in d.items():         
        yield get_generator(k, v)
...