Класс ChainMap()
предназначен для быстрого объединения нескольких словарей, чтобы их можно было рассматривать как единое целое. Такой контейнер объединяет словари и ищет ключи намного быстрее, чем создание нового словаря и выполнение объединения при помощи вызовов dict.update()
.
Класс ChainMap()
может использоваться для имитации вложенных областей и полезен при создании шаблонов. Смотрите примеры использования ChainMap
import collections d = collections.ChainMap(*maps)
*maps
- один или несколько словарей.ChainMap
.Класс 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'})