не понимаю это лямбда-выражение с defaultdict

Я видел этот пример в pythontips. Я не понимаю вторую строку, когда defaultdict принимает аргумент " tree "и возвращает"tree".

import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['color']['favor'] = "yellow"
# Works fine

после запуска этого кода я проверил тип some_dict

defaultdict(< function < lambda > at 0x7f19ae634048 >, 
            {'color': defaultdict(
                  < function < lambda > at 0x7f19ae634048 >, {'favor': 'yellow'})})

3 ответов


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

в этом примере мы определяем функцию рекурсивную лямбда, tree, который возвращает defaultdict конструктор которого tree. Давайте перепишем это, используя регулярные функции для ясности.

from collections import defaultdict
from pprint import pprint

def get_recursive_dict():
    return defaultdict(get_recursive_dict)

обратите внимание, что мы возвращаемся defaultdict(get_recursive_dict), а не defaultdict(get_recursive_dict()). Мы хотим пройти defaultdict вызываемый объект (т. е. функция get_recursive_dict). На самом деле звоню get_recursive_dict() приведет к бесконечной рекурсии.

если мы называем get_recursive_dict, получаем пустой defaultdict значение по умолчанию-функция get_recursive_dict.

recursive_dict = get_recursive_dict()
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x0000000004FFC4A8>, {})

давайте посмотрим это в действии. Создайте ключ 'alice' и соответствующее значение по умолчанию равно пустому defaultdict значение по умолчанию-функция get_recursive_dict. Обратите внимание, что это то же значение по умолчанию, что и наш recursive_dict!

print(recursive_dict['alice'])
# defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, {})
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, {'alice': defaultdict(<function get_recursive_dict at 0x0000000004AF46D8>, {})})

таким образом, мы можем создать столько вложенных словарей, сколько захотим.

recursive_dict['bob']['age'] = 2
recursive_dict['charlie']['food']['dessert'] = 'cake'
print(recursive_dict)
# defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, {'charlie': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, {'food': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, {'dessert': 'cake'})}), 'bob': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, {'age': 2}), 'alice': defaultdict(<function get_recursive_dict at 0x00000000049BD4A8>, {})})

после перезаписи значения по умолчанию ключом вы больше не можете создавать произвольно глубокие вложенные словари.

recursive_dict['bob']['age']['year'] = 2016
# TypeError: 'int' object does not support item assignment

надеюсь, это все прояснит!


два замечания:

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

Итак, вот 2 разных способа определения функционально идентичных объектов. Это рекурсивные функции, потому что они ссылаются на себя.

from collections import defaultdict

# anonymous
tree = lambda: defaultdict(tree)

# explicit
def tree(): return defaultdict(tree)

запуск последних 2 строк с этими различными определениями в свою очередь, вы видите только тонкий разница в названии defaultdict тип:

# anonymous
defaultdict(<function __main__.<lambda>()>,
            {'color': defaultdict(<function __main__.<lambda>()>,
                         {'favor': 'yellow'})})

# explicit
defaultdict(<function __main__.tree()>,
            {'color': defaultdict(<function __main__.tree()>,
                         {'favor': 'yellow'})})

легче увидеть, если вы попробуете это:a = lambda: a, вы увидите, что a() возвращает a. Так...

>>> a = lambda: a
>>> a()()()()
<function <lambda> at 0x102bffd08>

они делают это с помощью тега defaultdict тоже. tree - это функция, возвращающая defaultdict, значение по умолчанию которой является еще одним defaultdict, и так далее.

я тоже об этом не знал. Я думал tree сначала нужно было бы определить. Может быть, это специальное правило Python? (EDIT:) Нет, я забыл, что Python выполняет поиск имени во время выполнения, и tree уже указывает на лямбду. В C++ есть проверка ссылок во время компиляции, но вы можете определить функции, которые ссылаются на себя.

это похоже на способ создать поведение, которое некоторые пользователи не ожидали бы. Как сказать вам случайно переопределить tree позже, ваш defaultdict сломан:

>>> import collections
>>> tree = lambda: collections.defaultdict(tree)
>>> some_dict = tree()
>>> tree = 4
>>> some_dict[4][3] = 2 # TypeError: first argument must be callable or None