Сообщить об ошибке.

Класс ChainMap() модуля collections в Python

Обновляемый, производительный контейнер словарей dict()

Класс ChainMap() предназначен для быстрого объединения нескольких словарей, чтобы их можно было рассматривать как единое целое. Такой контейнер объединяет словари и ищет ключи намного быстрее, чем создание нового словаря и выполнение объединения при помощи вызовов dict.update().

Класс ChainMap() может использоваться для имитации вложенных областей и полезен при создании шаблонов. Смотрите примеры использования ChainMap

Синтаксис:

import collections

d = collections.ChainMap(*maps)

Параметры:

Возвращаемое значение:

Описание:

Класс ChainMap() модуля collections группирует несколько словарей или других сопоставлений для создания единого обновляемого представления. Если словари maps не указаны, то будет создан один пустой словарь.

>>> from collections import ChainMap
>>> first = {'two': 22, 'three': 3}
>>> last = {'one': 1, 'two': 2}
>>> d = ChainMap(first, last)
>>> d
# ChainMap({'two': 22, 'three': 3}, {'one': 1, 'two': 2})

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

Класс поддерживает все основные методы словарей dict(). Операции добавления, обновления и удаления значений могут быть произведены только со словарем, который был добавлен первым.

Новое в Python 3.9: Добавлена ​​поддержка операторов слияния словарей (|) и обновления словарей (|=).

>>> d['four'] = 4
>>> d
# ChainMap({'two': 22, 'three': 3, 'four': 4}, {'one': 1, 'two': 2})
>>> d.popitem()
('four', 4)
>>> d
# ChainMap({'two': 22, 'three': 3}, {'one': 1, 'two': 2})

# попробуем изменить первый элемент второго словаря
>>> d['one'] = 11
>>> d
# ChainMap({'two': 22, 'three': 3, 'one': 11}, {'one': 1, 'two': 2})
# все изменения происходят только с первым словарем

Класс ChainMap() добавляет и хранит словари по ссылкам. Таким образом, если обновляется один из исходных словарей, эти изменения будут отражены в ChainMap() и на оборот, все обновления произведенные со словарями через класс ChainMap() отразятся на исходных словарях.

# смотрим исходный словарь
>>> first
# {'two': 22, 'three': 3, 'one': 11}

# изменяем исходные словари
>>> del first['two']
>>> last['four'] = 4
# смотрим как изменился 'd' - экземпляр ChainMap()
>>> d
# ChainMap({'three': 3, 'one': 11}, {'one': 1, 'two': 2, 'four': 4})

Поиск ключей в ChainMap() происходит последовательно, слева на право, во всех добавленных словарях, пока не будет найден соответствующий ключ.

>>> d['one']
# 11
>>> d['two']
# 2

Обратите внимание, что порядок итераций ChainMap() происходит в обратном порядке от последнего добавленного словаря к первому:

>>> list(d)
# ['one', 'two', 'four', 'three']

Это дает тот же порядок, что и последовательность вызовов dict.update(), начиная с последнего сопоставления:

>>> combined = last.copy()
>>> combined.update(first)
>>> list(combined)
# ['one', 'two', 'four', 'three']

Объект ChainMap, атрибуты и методы:

ChainMap.maps:

Атрибут ChainMap.maps представляет собой обновляемый пользователем список словарей dict() и должен всегда содержать хотя бы один словарь. Список словарей ChainMap.maps упорядочен в порядке их добавления для последовательного поиска по ключам от первого до последнего. Это единственное сохраненное состояние, которое можно изменить.

>>> d.maps
# [{'three': 3, 'one': 11}, {'one': 1, 'two': 2, 'four': 4}]

Атрибут ChainMap.maps поддерживает все основные операции со списками, следовательно можно добавлять новые словари и удалять уже добавленные, а так же изменять их последовательность.

Обратите внимание, что изменяя последовательность словарей с списке атрибута d.maps, так же меняется последовательность поиска ключей и их значений.

# поиск ключа в экземпляре `ChainMap()`
>>> d['one']
# 11

# меняем последовательность словарей `ChainMap()`
>>> d.maps.reverse()
>>> d.maps
# [{'one': 1, 'two': 2, 'four': 4}, {'three': 3, 'one': 11}]

# ключ изменился
>>> d['one']
# 1

Через атрибут maps можно изменять ВСЕ словари. Доступ к конкретному словарю осуществляется по индексу в списке атрибута d.maps[i], а изменения осуществляются через их методы dict().

# доступ к словарям
>>> d.maps[0]
# {'one': 1, 'two': 2, 'four': 4}
>>> d.maps[1]['three']
# 3

# изменяем словари и не забываем, что мы 
# поменяли их местами - 'd.maps.reverse()' 
>>> d.maps[0]['five'] = 5
>>> del d.maps[0]['four']
>>> d.maps[1]['four'] = 4
>>> d
# ChainMap({'one': 1, 'two': 2, 'five': 5}, {'three': 3, 'one': 11, 'four': 4})

# исходные словари то же изменились
>>> first
# {'three': 3, 'one': 11, 'four': 4}
>>> last
# {'one': 1, 'two': 2, 'five': 5}

# изменяем список словарей
>>> d.maps.pop()
# {'three': 3, 'one': 11, 'four': 4}
>>> d
# ChainMap({'one': 1, 'two': 2, 'five': 5})

# добавляем в экземпляр `ChainMap()` новый словарь
>>> new_dict = {'a': 10, 'b': 20, 'c': 30}
>>> d.maps.append(new_dict)
>>> d
# ChainMap({'one': 1, 'two': 2, 'five': 5}, {'a': 10, 'b': 20, 'c': 30})
>>> del d.maps[1]['c']
>>> d.maps[0]['one'] = 0
>>> d
# ChainMap({'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

# исходные словари 
>>> last
{'one': 0, 'two': 2, 'five': 5}
>>> new_dict
{'a': 10, 'b': 20}

ChainMap.new_child(m=None):

Метод ChainMap.new_child() возвращает новый экземпляр класса ChainMap(), содержащий новый словарь m, за которым следуют все словари в текущем экземпляре.

  • Если указан словарь m, то он вставляется первым в списке существующих словарей текущего экземпляра.
  • Если m не указан, то используется пустой dict, так что вызов d.new_child() эквивалентен вызову ChainMap({}, *d.maps).

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

>>> d_child = d.new_child()
>>> d_child
# ChainMap({}, {'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

# 'd_child' это новый экземпляр 'ChainMap()'
>>> d_child = d.new_child({'one': 1, 'a': 1})
>>> d_child
# ChainMap({'one': 1, 'a': 1}, {'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

# Исходный экземпляр `d` класса `ChainMap()` не изменился
>>> d
# ChainMap({'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

ChainMap.parents:

Атрибут ChainMap.parents возвращает новый экземпляр класса ChainMap(), содержащий все словари в текущем экземпляре, кроме первого. Это полезно для пропуска первого словаря при поиске ключей.

Варианты использования аналогичны тем, которые используются для оператора nonlocal, используемого во вложенных областях. Варианты использования также параллельны для встроенной функции super().

Ссылка на d.parents эквивалентна вызову ChainMap(*d.maps[1:]).

>>> d_child.parents
# ChainMap({'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

>>> d.parents
# ChainMap({'a': 10, 'b': 20})

# Исходный экземпляры не изменяются
>>> d
# ChainMap({'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})
>>> d_child
# ChainMap({'one': 0, 'a': 1}, {'one': 0, 'two': 2, 'five': 5}, {'a': 10, 'b': 20})

Примеры использования ChainMap:

Моделирование поиска переменных интерпретатором Python :

from collections import ChainMap
import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))

Установка разрешения пользователю, указанному в командной строке, иметь приоритет над переменными среды окружения os.environ, которые, в свою очередь, имеют приоритет над значениями по умолчанию:

import os, argparse

defaults = {'color': 'red', 'user': 'guest'}

parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k: v for k, v in vars(namespace).items() if v is not None}

combined = ChainMap(command_line_args, os.environ, defaults)
print(combined['color'])
print(combined['user'])

Примеры шаблонов для использования класса collections.ChainMap() для имитации вложенных контекстов:

c = ChainMap()        # Create root context
d = c.new_child()     # Create nested child context
e = c.new_child()     # Child of c, independent from d
e.maps[0]             # Current context dictionary -- like Python's locals()
e.maps[-1]            # Root context -- like Python's globals()
e.parents             # Enclosing context chain -- like Python's nonlocals

d['x'] = 1            # Set value in current context
d['x']                # Get first key in the chain of contexts
del d['x']            # Delete from current context
list(d)               # All nested values
k in d                # Check all nested values
len(d)                # Number of nested values
d.items()             # All nested items
dict(d)               # Flatten into a regular dictionary

Класс collections.ChainMap() может обновлять (изменять, вставлять и удалять) значения ключей только в первом словаре, в то время как поиск ключей будет осуществляться по всем словарям в списке. Если необходимо обновлять все добавленные словари, то легко создать подкласс, который обновляет ключи, найденные по всей цепочке:

class DeepChainMap(ChainMap):
    'Variant of ChainMap that allows direct updates to inner scopes'

    def __setitem__(self, key, value):
        for mapping in self.maps:
            if key in mapping:
                mapping[key] = value
                return
        self.maps[0][key] = value

    def __delitem__(self, key):
        for mapping in self.maps:
            if key in mapping:
                del mapping[key]
                return
        raise KeyError(key)

>>> d = DeepChainMap({'zebra': 'black'}, {'elephant': 'blue'}, {'lion': 'yellow'})
>>> d['lion'] = 'orange'
# добавление происходит к первому словарю
>>> d['snake'] = 'red'
>>> del d['elephant']
>>> d
# DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'})