Почему yield не возвращает отдельные значения во время каждого цикла построения этого defaultdict?

#python #yield #defaultdict

#python #yield #defaultdict

Вопрос:

Это исходный код:

 from collections import defaultdict

lis = [[1, 2], [2, 1], [3, 0], [2, 1], [1, 1]]

res = defaultdict(int)
for i, j in lis:
    res[i]  = j
    print(res.items())
 

Результат

 dict_items([(1, 2)])
dict_items([(1, 2), (2, 1)])
dict_items([(1, 2), (2, 1), (3, 0)])
dict_items([(1, 2), (2, 2), (3, 0)])
dict_items([(1, 3), (2, 2), (3, 0)])
 

Я хочу использовать yield для получения этих печатных элементов.

 from collections import defaultdict

li = [[1, 2], [2, 1], [3, 0], [2, 1], [1, 1]]


def g(lis: list):
    res = defaultdict(int)
    for i, j in lis:
        res[i]  = j
        yield res.items()


print(*g(li))
 

но я получаю

 dict_items([(1, 3), (2, 2), (3, 0)]) dict_items([(1, 3), (2, 2), (3, 0)]) dict_items([(1, 3), (2, 2), (3, 0)]) dict_items([(1, 3), (2, 2), (3, 0)]) dict_items([(1, 3), (2, 2), (3, 0)])
 

Комментарии:

1. Давайте подтвердим намерение этого кода: он хочет создать dict из целочисленных ключей и целочисленных значений. Функциональность defaultdict фактически никогда не используется, например, она не создает список всех целочисленных значений, соответствующих этому ключу, поэтому, например, 5-я запись 1: 1 перезаписывает 1: 2. Это действительно ваше намерение?

Ответ №1:

То, что вы говорите в своем собственном ответе, является правдой. Я просто хотел убедиться, что вы понимаете, что этот факт, который вы обнаружили, был бы в равной степени верен для вашего первого примера кода, если бы вы собрали каждое значение в список, а затем распечатали этот список с помощью одного print оператора. yield это не имеет никакого отношения к проблеме, которую вы видите. Я ожидаю, что вы уже знаете это, но я хотел указать на это на случай, если кто-то, читающий это позже, может подумать, что это проблема, возникающая при использовании yield . Это не так.

Чтобы увидеть это, вы можете изменить свой второй пример, чтобы сразу напечатать полученное значение. Таким образом, вы делаете одно и то же в обоих примерах…печать следующего значения, как только оно будет сгенерировано. Если вы сделаете это, то получите один и тот же результат для обеих версий вашего кода.

Вот полный набор кода для демонстрации этого:

 from collections import defaultdict

lis = [[1, 2], [2, 1], [3, 0], [2, 1], [1, 1]]

res = defaultdict(int)
for i, j in lis:
    res[i]  = j
    print(res.items())

def g(lis: list):
    res = defaultdict(int)
    for i, j in lis:
        res[i]  = j
        yield res.items()

for v in g(lis):
    # Print the next generated value
    print(v)
 

Результат:

 dict_items([(1, 2)])
dict_items([(1, 2), (2, 1)])
dict_items([(1, 2), (2, 1), (3, 0)])
dict_items([(1, 2), (2, 2), (3, 0)])
dict_items([(1, 3), (2, 2), (3, 0)])
dict_items([(1, 2)])
dict_items([(1, 2), (2, 1)])
dict_items([(1, 2), (2, 1), (3, 0)])
dict_items([(1, 2), (2, 2), (3, 0)])
dict_items([(1, 3), (2, 2), (3, 0)])
 

Ответ №2:

Ну, я нахожу проблему.

Поскольку defaultdict это изменяемый объект.

Итак, мне нужно скопировать res (нет необходимости в deepcopy, поскольку элементы являются кортежами, которые неизменяемы)

 from collections import defaultdict
from copy import copy

li = [[1, 2], [2, 1], [3, 0], [2, 1], [1, 1]]


def g(lis: list):
    res = defaultdict(int)
    for i, j in lis:
        res[i]  = j
        yield copy(res)


print([x.items() for x in g(li)])
 

или непосредственно вернуть .items()

 from collections import defaultdict
from copy import copy

li = [[1, 2], [2, 1], [3, 0], [2, 1], [1, 1]]


def g(lis: list):
    res = defaultdict(int)
    for i, j in lis:
        res[i]  = j
        yield copy(list(res.items()))
        # reason to add list() is TypeError: cannot pickle 'dict_items' object


print(*g(li))
 

Или используйте list() для создания нового объекта

 def g(lis: list):
    res = defaultdict(int)
    for i, j in lis:
        res[i]  = j
        yield list(res.items())