Spec-Zone.ru › Python 3.12

collections — Типы данных-контейнеры

Исходный код: Lib/collections/__init__.py

Этот модуль реализует специализированные типы данных-контейнеров, предлагающие альтернативы встроенным контейнерам Python общего назначения: dict, list, set и tuple.

namedtuple()

фабричная функция для создания подклассов кортежей с именованными полями

deque

контейнер, похожий на список, с быстрым добавлением и удалением элементов с обоих концов

ChainMap

класс, похожий на dict, для создания единого представления нескольких отображений

Counter

подкласс dict для подсчёта объектов с поддержкой хеширования

OrderedDict

подкласс dict, запоминающий порядок добавления записей

defaultdict

подкласс dict, вызывающий фабричную функцию для предоставления отсутствующих значений

UserDict

обёртка над объектами словарей, упрощающая создание подклассов dict

UserList

обёртка над объектами списков, упрощающая создание подклассов list

UserString

обёртка над строковыми объектами, упрощающая создание подклассов str

Объекты ChainMap

Добавлено в версии 3.3.

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

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

class collections.ChainMap(*maps)

Объект ChainMap объединяет несколько словарей или других отображений, создавая единое представление, которое можно обновлять. Если аргумент maps не указан, создаётся один пустой словарь, поэтому новая цепочка всегда содержит как минимум одно отображение.

Базовые отображения хранятся в списке. Этот список является открытым атрибутом maps, к нему можно обращаться и изменять его. Другого состояния нет.

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

Объект ChainMap включает базовые отображения по ссылке. Поэтому изменения в любом из базовых отображений будут отражены в ChainMap.

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

maps

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

new_child(m=None, **kwargs)

Возвращает новый объект ChainMap, содержащий новое отображение, за которым следуют все отображения текущего экземпляра. Если указан аргумент m, он становится новым первым отображением в списке; если аргумент не указан, используется пустой dict, поэтому вызов d.new_child() эквивалентен: ChainMap({}, *d.maps). Если указаны именованные аргументы, они обновляют переданное отображение или новый пустой dict. Этот метод используется для создания подчинённых контекстов, которые можно обновлять, не изменяя значения в родительских отображениях.

Изменено в версии 3.4: Добавлен необязательный параметр m.

Изменено в версии 3.10: Добавлена поддержка именованных аргументов.

parents

Свойство, возвращающее новый объект ChainMap, содержащий все отображения текущего экземпляра, кроме первого. Это удобно, когда нужно пропустить первое отображение при поиске. Сценарии использования похожи на применение ключевого слова nonlocal во вложенных областях видимости. Они также аналогичны сценариям использования встроенной функции super(). Ссылка на d.parents эквивалентна: ChainMap(*d.maps[1:]).

Обратите внимание: порядок обхода объекта ChainMap определяется просмотром отображений от последнего к первому:

>>> baseline = {'music': 'bach', 'art': 'rembrandt'}
>>> adjustments = {'art': 'van gogh', 'opera': 'carmen'}
>>> list(ChainMap(adjustments, baseline))
['music', 'art', 'opera']

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

>>> combined = baseline.copy()
>>> combined.update(adjustments)
>>> list(combined)
['music', 'art', 'opera']

Изменено в версии 3.9: Добавлена поддержка операторов | и |=, описанных в PEP 584.

См. также

  • Класс MultiContext из пакета CodeTools компании Enthought поддерживает запись в любое отображение цепочки.
  • Класс Context из Django для работы с шаблонами представляет собой цепочку отображений, доступную только для чтения. Он также поддерживает добавление и удаление контекстов, подобно методу new_child() и свойству parents.
  • В рецепте Вложенные контексты можно управлять тем, применяются ли записи и другие изменения только к первому отображению или к любому отображению цепочки.
  • Сильно упрощённая версия ChainMap, доступная только для чтения.

Примеры и рецепты ChainMap

В этом разделе показаны различные способы работы с цепочками отображений.

Пример имитации внутренней цепочки поиска Python:

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

Пример, в котором аргументы командной строки, заданные пользователем, имеют приоритет над переменными окружения, а те, в свою очередь, — над значениями по умолчанию:

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'])

Примеры использования класса 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

Класс 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'         # update an existing key two levels down
>>> d['snake'] = 'red'           # new keys get added to the topmost dict
>>> del d['elephant']            # remove an existing key one level down
>>> d                            # display result
DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'})

Объекты Counter

Для удобного и быстрого подсчёта предоставляется инструмент Counter. Например:

>>> # Tally occurrences of words in a list
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] += 1
...
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

>>> # Find the ten most common words in Hamlet
>>> import re
>>> words = re.findall(r'\w+', open('hamlet.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
 ('you', 554),  ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
class collections.Counter([iterable-or-mapping])

Объект Counter — это подкласс dict, предназначенный для подсчёта объектов с поддержкой хеширования. Это коллекция, в которой элементы хранятся в качестве ключей словаря, а их количества — в качестве значений. Количеством может быть любое целое число, в том числе ноль или отрицательное число. Класс Counter похож на мультимножества в других языках.

Элементы подсчитываются из итерируемого объекта или инициализируются из другого отображения (или счётчика):

>>> c = Counter()                           # a new, empty counter
>>> c = Counter('gallahad')                 # a new counter from an iterable
>>> c = Counter({'red': 4, 'blue': 2})      # a new counter from a mapping
>>> c = Counter(cats=4, dogs=8)             # a new counter from keyword args

Объекты Counter имеют интерфейс словаря, но для отсутствующих элементов возвращают нулевое количество, а не возбуждают исключение KeyError:

>>> c = Counter(['eggs', 'ham'])
>>> c['bacon']                              # count of a missing element is zero
0

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

>>> c['sausage'] = 0                        # counter entry with a zero count
>>> del c['sausage']                        # del actually removes the entry

Добавлено в версии 3.1.

Изменено в версии 3.7: Будучи подклассом dict, класс Counter унаследовал возможность запоминать порядок вставки. Математические операции над объектами Counter также сохраняют порядок. Результаты упорядочены по моменту первого появления элемента в левом операнде, а затем — по порядку появления в правом операнде.

Помимо методов, доступных для всех словарей, объекты Counter поддерживают дополнительные методы:

elements()

Возвращает итератор по элементам, повторяя каждый элемент столько раз, сколько указано в его количестве. Элементы возвращаются в порядке первого появления. Если количество элемента меньше единицы, метод elements() игнорирует его.

>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
most_common([n])

Возвращает список из n наиболее часто встречающихся элементов и их количеств, от наиболее частых к наименее частым. Если n не указано или равно None, метод most_common() возвращает все элементы счётчика. Элементы с одинаковым количеством упорядочены по моменту первого появления:

>>> Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
subtract([iterable-or-mapping])

Вычитает элементы, полученные из итерируемого объекта или другого отображения (или счётчика). Подобно методу dict.update(), но вычитает количества, а не заменяет их. И входные, и выходные значения могут быть нулевыми или отрицательными.

>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> d = Counter(a=1, b=2, c=3, d=4)
>>> c.subtract(d)
>>> c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})

Добавлено в версии 3.2.

total()

Вычисляет сумму количеств.

>>> c = Counter(a=10, b=5, c=0)
>>> c.total()
15

Добавлено в версии 3.10.

Для объектов Counter доступны обычные методы словаря, за исключением двух методов, работающих со счётчиками иначе.

fromkeys(iterable)

Этот метод класса не реализован для объектов Counter.

update([iterable-or-mapping])

Подсчитывает элементы из итерируемого объекта или добавляет значения из другого отображения (или счётчика). Подобно методу dict.update(), но складывает количества, а не заменяет их. Кроме того, ожидается, что итерируемый объект будет последовательностью элементов, а не последовательностью пар (key, value).

Для счётчиков поддерживаются операторы сравнения на равенство, включение и включение в качестве надмножества: ==, !=, <, <=, >, >=. Во всех этих проверках отсутствующие элементы считаются имеющими нулевое количество, поэтому Counter(a=1) == Counter(a=1, b=0) возвращает True.

Изменено в версии 3.10: Добавлены расширенные операции сравнения.

Изменено в версии 3.10: При проверке равенства отсутствующие элементы считаются имеющими нулевое количество. Ранее Counter(a=3) и Counter(a=3, b=0) считались различными.

Распространённые шаблоны работы с объектами Counter:

c.total()                       # total of all counts
c.clear()                       # reset all counts
list(c)                         # list unique elements
set(c)                          # convert to a set
dict(c)                         # convert to a regular dictionary
c.items()                       # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs))    # convert from a list of (elem, cnt) pairs
c.most_common()[:-n-1:-1]       # n least common elements
+c                              # remove zero and negative counts

Для комбинирования объектов Counter и получения мультимножеств (счётчиков с положительными количествами) предусмотрено несколько математических операций. Сложение и вычитание объединяют счётчики, складывая или вычитая количества соответствующих элементов. Пересечение и объединение возвращают минимальные и максимальные количества соответствующих элементов. Равенство и включение сравнивают соответствующие количества. Каждая операция может принимать аргументы с количеством со знаком, но в результат не включаются элементы с нулевым или отрицательным количеством.

>>> c = Counter(a=3, b=1)
>>> d = Counter(a=1, b=2)
>>> c + d                       # add two counters together:  c[x] + d[x]
Counter({'a': 4, 'b': 3})
>>> c - d                       # subtract (keeping only positive counts)
Counter({'a': 2})
>>> c & d                       # intersection:  min(c[x], d[x])
Counter({'a': 1, 'b': 1})
>>> c | d                       # union:  max(c[x], d[x])
Counter({'a': 3, 'b': 2})
>>> c == d                      # equality:  c[x] == d[x]
False
>>> c <= d                      # inclusion:  c[x] <= d[x]
False

Унарные сложение и вычитание — это сокращённые формы сложения с пустым счётчиком и вычитания из пустого счётчика соответственно.

>>> c = Counter(a=2, b=-4)
>>> +c
Counter({'a': 2})
>>> -c
Counter({'b': 4})

Добавлено в версии 3.3: Добавлена поддержка унарного плюса, унарного минуса и операций над мультимножествами с присваиванием.

Примечание

Счётчики в первую очередь предназначены для работы с положительными целыми числами, представляющими текущие количества; однако при их проектировании старались не исключать без необходимости сценарии использования с другими типами или отрицательными значениями. Для таких сценариев в этом разделе описаны минимальные ограничения на диапазон и типы.

  • Сам класс Counter — это подкласс словаря без ограничений на ключи и значения. Предполагается, что значения являются числами, представляющими количества, но в поле значения можно хранить что угодно.
  • Методу most_common() требуется лишь, чтобы значения можно было упорядочить.
  • Для операций с присваиванием, таких как c[key] += 1, тип значения должен поддерживать только сложение и вычитание. Поэтому подойдут дроби, числа с плавающей точкой и десятичные числа; допускаются и отрицательные значения. То же верно для методов update() и subtract(), которые допускают отрицательные и нулевые значения как на входе, так и на выходе.
  • Методы для мультимножеств предназначены только для сценариев с положительными значениями. Входные значения могут быть отрицательными или нулевыми, но создаются только результаты с положительными значениями. Ограничений на типы нет, однако тип значения должен поддерживать сложение, вычитание и сравнение.
  • Методу elements() требуются целочисленные количества. Он игнорирует нулевые и отрицательные количества.

См. также

  • Класс Bag в Smalltalk.
  • Статья в Википедии о мультимножествах.
  • Руководство по мультимножествам C++ с примерами.
  • О математических операциях над мультимножествами и сценариях их применения см. Кнут, Дональд. Искусство программирования, том II, раздел 4.6.3, упражнение 19.
  • Чтобы перечислить все различные мультимножества заданного размера над заданным набором элементов, см. itertools.combinations_with_replacement():

    map(Counter, combinations_with_replacement('ABC', 2)) # --> AA AB AC BB BC CC
    

Объекты deque

class collections.deque([iterable[, maxlen]])

Возвращает новый объект deque, инициализированный данными из итерируемого объекта слева направо (с использованием append()). Если итерируемый объект не указан, новый deque будет пустым.

Deque представляют собой обобщение стеков и очередей (название произносится «дек» и является сокращением от «двусторонняя очередь»). Deque поддерживают потокобезопасное и экономное по памяти добавление и удаление элементов с любого конца с приблизительно одинаковой производительностью O(1) в обоих направлениях.

Хотя объекты list поддерживают похожие операции, они оптимизированы для быстрых операций фиксированной длины и требуют перемещения памяти объёма O(n) для операций pop(0) и insert(0, v), которые изменяют и размер, и положение базового представления данных.

Если maxlen не указан или равен None, deque может расти до произвольной длины. В противном случае длина deque ограничивается заданным максимумом. Когда deque с ограниченной длиной заполнен, при добавлении новых элементов соответствующее число элементов удаляется с противоположного конца. Deque с ограниченной длиной обеспечивают функциональность, подобную фильтру tail в Unix. Они также полезны для отслеживания транзакций и других наборов данных, где важна только самая недавняя активность.

Объекты deque поддерживают следующие методы:

append(x)

Добавляет x в правый конец deque.

appendleft(x)

Добавляет x в левый конец deque.

clear()

Удаляет все элементы из deque, оставляя его пустым.

copy()

Создаёт поверхностную копию deque.

Добавлено в версии 3.5.

count(x)

Подсчитывает количество элементов deque, равных x.

Добавлено в версии 3.2.

extend(iterable)

Расширяет правый конец deque, добавляя элементы из аргумента-итерируемого объекта.

extendleft(iterable)

Расширяет левый конец deque, добавляя элементы из итерируемого объекта. Обратите внимание: последовательность добавлений слева приводит к обращению порядка элементов в аргументе-итерируемом объекте.

index(x[, start[, stop]])

Возвращает позицию x в deque (на индексе start или после него и до индекса stop). Возвращает первое совпадение или возбуждает исключение ValueError, если элемент не найден.

Добавлено в версии 3.5.

insert(i, x)

Вставляет x в deque на позицию i.

Если вставка приведёт к превышению maxlen для deque с ограниченной длиной, возбуждается исключение IndexError.

Добавлено в версии 3.5.

pop()

Удаляет и возвращает элемент с правого конца deque. Если элементов нет, возбуждает исключение IndexError.

popleft()

Удаляет и возвращает элемент с левого конца deque. Если элементов нет, возбуждает исключение IndexError.

remove(value)

Удаляет первое вхождение значения value. Если значение не найдено, возбуждает исключение ValueError.

reverse()

Обращает порядок элементов deque на месте и возвращает None.

Добавлено в версии 3.2.

rotate(n=1)

Выполняет циклический сдвиг deque на n шагов вправо. Если n отрицательно, выполняется сдвиг влево.

Для непустого deque сдвиг на один шаг вправо эквивалентен d.appendleft(d.pop()), а сдвиг на один шаг влево эквивалентен d.append(d.popleft()).

Объекты deque также предоставляют один атрибут только для чтения:

maxlen

Максимальный размер deque или None, если его длина не ограничена.

Добавлено в версии 3.1.

Кроме перечисленного выше, deque поддерживают итерацию, сериализацию с помощью pickle, len(d), reversed(d), copy.copy(d), copy.deepcopy(d), проверку принадлежности с помощью оператора in и обращение по индексу, например d[0], для доступа к первому элементу. Доступ по индексу имеет сложность O(1) на обоих концах, но замедляется до O(n) в середине. Для быстрого произвольного доступа используйте списки.

Начиная с версии 3.5, deque поддерживают __add__(), __mul__() и __imul__().

Пример:

>>> from collections import deque
>>> d = deque('ghi')                 # make a new deque with three items
>>> for elem in d:                   # iterate over the deque's elements
...     print(elem.upper())
G
H
I

>>> d.append('j')                    # add a new entry to the right side
>>> d.appendleft('f')                # add a new entry to the left side
>>> d                                # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])

>>> d.pop()                          # return and remove the rightmost item
'j'
>>> d.popleft()                      # return and remove the leftmost item
'f'
>>> list(d)                          # list the contents of the deque
['g', 'h', 'i']
>>> d[0]                             # peek at leftmost item
'g'
>>> d[-1]                            # peek at rightmost item
'i'

>>> list(reversed(d))                # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d                         # search the deque
True
>>> d.extend('jkl')                  # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1)                      # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1)                     # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])

>>> deque(reversed(d))               # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear()                        # empty the deque
>>> d.pop()                          # cannot pop from an empty deque
Traceback (most recent call last):
    File "<pyshell#6>", line 1, in -toplevel-
        d.pop()
IndexError: pop from an empty deque

>>> d.extendleft('abc')              # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])

Рецепты deque

В этом разделе показаны различные способы работы с deque.

Deque с ограниченной длиной обеспечивают функциональность, подобную фильтру tail в Unix:

def tail(filename, n=10):
    'Return the last n lines of a file'
    with open(filename) as f:
        return deque(f, n)

Другой способ использования deque — хранить последовательность недавно добавленных элементов, добавляя их справа и удаляя слева:

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # https://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

Планировщик циклического обхода можно реализовать, храня входные итераторы в объекте deque. Значения извлекаются из активного итератора, находящегося в позиции ноль. Если итератор исчерпан, его можно удалить с помощью popleft(); в противном случае его можно переместить в конец с помощью метода rotate():

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    iterators = deque(map(iter, iterables))
    while iterators:
        try:
            while True:
                yield next(iterators[0])
                iterators.rotate(-1)
        except StopIteration:
            # Remove an exhausted iterator.
            iterators.popleft()

Метод rotate() позволяет реализовать срезы и удаление элементов из deque. Например, реализация del d[n] на чистом Python использует метод rotate() для перемещения элементов, которые нужно извлечь:

def delete_nth(d, n):
    d.rotate(-n)
    d.popleft()
    d.rotate(n)

Чтобы реализовать срезы deque, используйте похожий подход, применяя rotate() для перемещения нужного элемента на левый конец deque. Удаляйте старые записи с помощью popleft(), добавляйте новые с помощью extend(), а затем отменяйте сдвиг. Небольшие изменения этого подхода позволяют легко реализовать стековые операции в стиле Forth, такие как dup, drop, swap, over, pick, rot и roll.

Объекты defaultdict

class collections.defaultdict(default_factory=None, /[, ...])

Возвращает новый объект, подобный словарю. defaultdict является подклассом встроенного класса dict. Он переопределяет один метод и добавляет одну доступную для записи переменную экземпляра. Остальные возможности совпадают с возможностями класса dict и здесь не документируются.

Первый аргумент задаёт начальное значение атрибута default_factory; по умолчанию используется None. Все остальные аргументы обрабатываются так же, как если бы они были переданы конструктору dict, включая именованные аргументы.

Объекты defaultdict поддерживают следующий метод в дополнение к стандартным операциям dict:

__missing__(key)

Если атрибут default_factory равен None, возбуждается исключение KeyError, которому в качестве аргумента передаётся ключ.

Если default_factory не равен None, он вызывается без аргументов, чтобы предоставить значение по умолчанию для заданного ключа; это значение добавляется в словарь для ключа и возвращается.

Если при вызове default_factory возникает исключение, оно передаётся без изменений.

Этот метод вызывается методом __getitem__() класса dict, когда запрошенный ключ не найден; всё, что он возвращает или возбуждает, затем возвращается или возбуждается методом __getitem__().

Обратите внимание, что __missing__() вызывается только для операций __getitem__(). Это означает, что get(), как и для обычных словарей, возвращает None по умолчанию, а не использует default_factory.

Объекты defaultdict поддерживают следующую переменную экземпляра:

default_factory

Этот атрибут используется методом __missing__(); он инициализируется первым аргументом конструктора, если он указан, или значением None, если аргумент отсутствует.

Изменено в версии 3.9: Добавлены операторы слияния (|) и обновления (|=), описанные в PEP 584.

defaultdict Примеры

Если использовать list в качестве default_factory, последовательность пар «ключ-значение» легко сгруппировать в словарь списков:

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

При первом появлении каждого ключа его ещё нет в отображении, поэтому запись автоматически создаётся с помощью функции default_factory, возвращающей пустой list. Затем операция list.append() добавляет значение в новый список. При повторном появлении ключа поиск выполняется обычным образом (возвращая список для этого ключа), а операция list.append() добавляет в список ещё одно значение. Этот способ проще и быстрее, чем аналогичный способ с использованием dict.setdefault():

>>> d = {}
>>> for k, v in s:
...     d.setdefault(k, []).append(v)
...
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

Если установить для default_factory значение int, defaultdict станет полезен для подсчёта (как мешок или мультимножество в других языках):

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> sorted(d.items())
[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

При первом появлении буквы её нет в отображении, поэтому функция default_factory вызывает int(), чтобы получить начальное значение счётчика, равное нулю. Затем операция увеличения накапливает количество вхождений каждой буквы.

Функция int(), всегда возвращающая ноль, — это частный случай функции-константы. Более быстрый и гибкий способ создания функций-констант — использовать лямбда-функцию, которая может возвращать любое постоянное значение (не только ноль):

>>> def constant_factory(value):
...     return lambda: value
...
>>> d = defaultdict(constant_factory('<missing>'))
>>> d.update(name='John', action='ran')
>>> '%(name)s %(action)s to %(object)s' % d
'John ran to <missing>'

Если установить для default_factory значение set, defaultdict станет полезен для создания словаря множеств:

>>> s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
>>> d = defaultdict(set)
>>> for k, v in s:
...     d[k].add(v)
...
>>> sorted(d.items())
[('blue', {2, 4}), ('red', {1, 3})]

Фабричная функция namedtuple() для кортежей с именованными полями

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

collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)

Возвращает новый подкласс кортежа с именем typename. Новый подкласс используется для создания объектов, подобных кортежам, к полям которых можно обращаться по атрибутам, а также по индексу и при итерации. Экземпляры подкласса также имеют информативную строку документации (с указанием typename и field_names) и удобный метод __repr__(), который перечисляет содержимое кортежа в формате name=value.

field_names — это последовательность строк, например ['x', 'y']. В качестве альтернативы field_names может быть одной строкой, в которой имена полей разделены пробелами и/или запятыми, например 'x y' или 'x, y'.

В качестве имени поля можно использовать любой допустимый идентификатор Python, кроме имён, начинающихся с подчёркивания. Допустимые идентификаторы состоят из букв, цифр и подчёркиваний, но не начинаются с цифры или подчёркивания и не могут быть keyword, например class, for, return, global, pass или raise.

Если rename имеет значение true, недопустимые имена полей автоматически заменяются позиционными именами. Например, ['abc', 'def', 'ghi', 'abc'] преобразуется в ['abc', '_1', 'ghi', '_3'], что устраняет ключевое слово def и дублирующееся имя поля abc.

defaults может быть равен None или быть итерируемым объектом со значениями по умолчанию. Поскольку поля со значением по умолчанию должны следовать после всех полей без значения по умолчанию, значения из defaults применяются к крайним правым параметрам. Например, если имена полей — ['x', 'y', 'z'], а значения по умолчанию — (1, 2), то x будет обязательным аргументом, для y значением по умолчанию будет 1, а для z — 2.

Если задан параметр module, атрибут __module__ именованного кортежа устанавливается в это значение.

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

Для поддержки сериализации с помощью pickle класс именованного кортежа следует присвоить переменной, совпадающей с typename.

Изменено в версии 3.1: Добавлена поддержка параметра rename.

Изменено в версии 3.6: Параметры verbose и rename стали только именованными аргументами.

Изменено в версии 3.6: Добавлен параметр module.

Изменено в версии 3.7: Удалены параметр verbose и атрибут _source.

Изменено в версии 3.7: Добавлены параметр defaults и атрибут _field_defaults.

>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

Именованные кортежи особенно полезны для присвоения имён полям в кортежах результатов, возвращаемых модулями csv или sqlite3:

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)

import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

Помимо методов, унаследованных от кортежей, именованные кортежи поддерживают три дополнительных метода и два атрибута. Чтобы избежать конфликтов с именами полей, имена методов и атрибутов начинаются с подчёркивания.

classmethod somenamedtuple._make(iterable)

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

>>> t = [11, 22]
>>> Point._make(t)
Point(x=11, y=22)
somenamedtuple._asdict()

Возвращает новый dict, сопоставляющий имена полей соответствующим значениям:

>>> p = Point(x=11, y=22)
>>> p._asdict()
{'x': 11, 'y': 22}

Изменено в версии 3.1: Возвращает OrderedDict вместо обычного dict.

Изменено в версии 3.8: Возвращает обычный dict вместо OrderedDict. Начиная с Python 3.7 гарантируется упорядоченность обычных словарей. Если необходимы дополнительные возможности OrderedDict, рекомендуется привести результат к нужному типу: OrderedDict(nt._asdict()).

somenamedtuple._replace(**kwargs)

Возвращает новый экземпляр именованного кортежа, заменяя указанные поля новыми значениями:

>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)

>>> for partnum, record in inventory.items():
...     inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now())
somenamedtuple._fields

Кортеж строк со списком имён полей. Полезен для интроспекции и создания новых типов именованных кортежей на основе существующих.

>>> p._fields            # view the field names
('x', 'y')

>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)
somenamedtuple._field_defaults

Словарь, сопоставляющий имена полей значениям по умолчанию.

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._field_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

Чтобы получить поле, имя которого хранится в строке, используйте функцию getattr():

>>> getattr(p, 'x')
11

Чтобы преобразовать словарь в именованный кортеж, используйте оператор двойной звёздочки (как описано в разделе Распаковка списков аргументов):

>>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)

Поскольку именованный кортеж — это обычный класс Python, его функциональность легко расширить или изменить с помощью подкласса. Вот как добавить вычисляемое поле и формат вывода фиксированной ширины:

>>> class Point(namedtuple('Point', ['x', 'y'])):
...     __slots__ = ()
...     @property
...     def hypot(self):
...         return (self.x ** 2 + self.y ** 2) ** 0.5
...     def __str__(self):
...         return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

>>> for p in Point(3, 4), Point(14, 5/7):
...     print(p)
Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

Показанный выше подкласс задаёт для __slots__ пустой кортеж. Это помогает снизить требования к памяти, предотвращая создание словарей экземпляров.

Создание подкласса не подходит для добавления новых сохраняемых полей. Вместо этого создайте новый тип именованного кортежа на основе атрибута _fields:

>>> Point3D = namedtuple('Point3D', Point._fields + ('z',))

Строки документации можно настроить, присвоив значения непосредственно полям __doc__:

>>> Book = namedtuple('Book', ['id', 'title', 'authors'])
>>> Book.__doc__ += ': Hardcover book in active collection'
>>> Book.id.__doc__ = '13-digit ISBN'
>>> Book.title.__doc__ = 'Title of first printing'
>>> Book.authors.__doc__ = 'List of authors sorted by last name'

Изменено в версии 3.5: Строки документации свойств стали доступны для записи.

См. также

  • См. typing.NamedTuple, чтобы узнать, как добавлять подсказки типов для именованных кортежей. Этот модуль также предлагает элегантный синтаксис с использованием ключевого слова class:

    class Component(NamedTuple):
        part_number: int
        weight: float
        description: Optional[str] = None
    
  • См. types.SimpleNamespace() — изменяемое пространство имён на основе словаря, а не кортежа.
  • Модуль dataclasses предоставляет декоратор и функции для автоматического добавления сгенерированных специальных методов в пользовательские классы.

Объекты OrderedDict

Упорядоченные словари похожи на обычные словари, но имеют дополнительные возможности для операций, связанных с порядком элементов. Теперь они менее важны, поскольку встроенный класс dict научился сохранять порядок вставки (это поведение было гарантировано начиная с Python 3.7).

Однако между ними и dict всё ещё есть некоторые различия:

  • Обычный dict разработан для эффективного выполнения операций сопоставления. Отслеживание порядка вставки было второстепенной задачей.
  • OrderedDict разработан для эффективного выполнения операций переупорядочивания. Эффективность использования памяти, скорость итерации и производительность операций обновления были второстепенными задачами.
  • Алгоритм OrderedDict лучше справляется с частыми операциями переупорядочивания, чем dict. Как показано в приведённых ниже рецептах, это делает его подходящим для реализации различных видов кэшей LRU.
  • При сравнении на равенство объектов OrderedDict учитывается совпадение порядка.

    Обычный dict может имитировать проверку равенства с учётом порядка с помощью p == q and all(k1 == k2 for k1, k2 in zip(p, q)).

  • Метод popitem() класса OrderedDict имеет другую сигнатуру. Он принимает необязательный аргумент, указывающий, какой элемент извлечь.

    Обычный dict может имитировать поведение OrderedDict для od.popitem(last=True) с помощью d.popitem(), который гарантированно извлекает крайний правый (последний) элемент.

    Обычный dict может имитировать поведение OrderedDict для od.popitem(last=False) с помощью (k := next(iter(d)), d.pop(k)), который возвращает и удаляет крайний левый (первый) элемент, если он существует.

  • В OrderedDict есть метод move_to_end(), позволяющий эффективно переместить элемент в один из концов.

    Обычный dict может имитировать поведение OrderedDict для od.move_to_end(k, last=True) с помощью d[k] = d.pop(k), который перемещает ключ и связанное с ним значение в крайнюю правую (последнюю) позицию.

    У обычного dict нет эффективного аналога od.move_to_end(k, last=False) из OrderedDict, который перемещает ключ и связанное с ним значение в крайнюю левую (первую) позицию.

  • До Python 3.8 у dict не было метода __reversed__().
class collections.OrderedDict([items])

Возвращает экземпляр подкласса dict со специализированными методами для изменения порядка элементов словаря.

Добавлено в версии 3.1.

popitem(last=True)

Метод popitem() упорядоченных словарей возвращает и удаляет пару (ключ, значение). Пары возвращаются в порядке LIFO (последним пришёл — первым ушёл), если last равно true, или в порядке FIFO (первым пришёл — первым ушёл), если оно равно false.

move_to_end(key, last=True)

Перемещает существующий ключ в один из концов упорядоченного словаря. Если last равно true (значение по умолчанию), элемент перемещается в конец; если last равно false — в начало. Если ключ не существует, возбуждается исключение KeyError:

>>> d = OrderedDict.fromkeys('abcde')
>>> d.move_to_end('b')
>>> ''.join(d)
'acdeb'
>>> d.move_to_end('b', last=False)
>>> ''.join(d)
'bacde'

Добавлено в версии 3.2.

Помимо обычных методов сопоставления, упорядоченные словари поддерживают обратную итерацию с помощью reversed().

Равенство объектов OrderedDict проверяется с учётом порядка и примерно эквивалентно list(od1.items())==list(od2.items()).

Равенство объектов OrderedDict и других объектов Mapping проверяется без учёта порядка, как и для обычных словарей. Благодаря этому объекты OrderedDict можно использовать везде, где применяются обычные словари.

Изменено в версии 3.5: Представления элементов, ключей и значений OrderedDict теперь поддерживают обратную итерацию с помощью reversed().

Изменено в версии 3.6: После принятия PEP 468 сохраняется порядок именованных аргументов, передаваемых конструктору OrderedDict и его методу update().

Изменено в версии 3.9: Добавлены операторы слияния (|) и обновления (|=), описанные в PEP 584.

OrderedDict Примеры и рецепты

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

class LastUpdatedOrderedDict(OrderedDict):
    'Store items in the order the keys were last added'

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self.move_to_end(key)

OrderedDict также пригодится для реализации вариантов functools.lru_cache():

from collections import OrderedDict
from time import time

class TimeBoundedLRU:
    "LRU Cache that invalidates and refreshes old entries."

    def __init__(self, func, maxsize=128, maxage=30):
        self.cache = OrderedDict()      # { args : (timestamp, result)}
        self.func = func
        self.maxsize = maxsize
        self.maxage = maxage

    def __call__(self, *args):
        if args in self.cache:
            self.cache.move_to_end(args)
            timestamp, result = self.cache[args]
            if time() - timestamp <= self.maxage:
                return result
        result = self.func(*args)
        self.cache[args] = time(), result
        if len(self.cache) > self.maxsize:
            self.cache.popitem(0)
        return result
class MultiHitLRUCache:
    """ LRU cache that defers caching a result until
        it has been requested multiple times.

        To avoid flushing the LRU cache with one-time requests,
        we don't cache until a request has been made more than once.

    """

    def __init__(self, func, maxsize=128, maxrequests=4096, cache_after=1):
        self.requests = OrderedDict()   # { uncached_key : request_count }
        self.cache = OrderedDict()      # { cached_key : function_result }
        self.func = func
        self.maxrequests = maxrequests  # max number of uncached requests
        self.maxsize = maxsize          # max number of stored return values
        self.cache_after = cache_after

    def __call__(self, *args):
        if args in self.cache:
            self.cache.move_to_end(args)
            return self.cache[args]
        result = self.func(*args)
        self.requests[args] = self.requests.get(args, 0) + 1
        if self.requests[args] <= self.cache_after:
            self.requests.move_to_end(args)
            if len(self.requests) > self.maxrequests:
                self.requests.popitem(0)
        else:
            self.requests.pop(args, None)
            self.cache[args] = result
            if len(self.cache) > self.maxsize:
                self.cache.popitem(0)
        return result

Объекты UserDict

Класс UserDict служит обёрткой для объектов-словарей. Необходимость в этом классе отчасти отпала благодаря возможности создавать подклассы непосредственно от dict; однако с этим классом может быть проще работать, поскольку базовый словарь доступен как атрибут.

class collections.UserDict([initialdata])

Класс, имитирующий словарь. Содержимое экземпляра хранится в обычном словаре, доступном через атрибут data экземпляров UserDict. Если задан initialdata, атрибут data инициализируется его содержимым; обратите внимание, что ссылка на initialdata не сохраняется, поэтому этот объект можно использовать и для других целей.

Помимо поддержки методов и операций отображений, экземпляры UserDict предоставляют следующий атрибут:

data

Настоящий словарь, используемый для хранения содержимого класса UserDict.

Объекты UserList

Этот класс служит обёрткой для объектов-списков. Он полезен как базовый класс для собственных классов, подобных спискам: от него можно наследовать, переопределять существующие методы или добавлять новые. Так можно расширить поведение списков.

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

class collections.UserList([list])

Класс, имитирующий список. Содержимое экземпляра хранится в обычном списке, доступном через атрибут data экземпляров UserList. Содержимое экземпляра изначально задаётся копией list; по умолчанию используется пустой список []. list может быть любым итерируемым объектом, например обычным списком Python или объектом UserList.

Помимо поддержки методов и операций изменяемых последовательностей, экземпляры UserList предоставляют следующий атрибут:

data

Настоящий объект list, используемый для хранения содержимого класса UserList.

Требования к подклассам: Ожидается, что подклассы UserList будут предоставлять конструктор, который можно вызвать без аргументов или с одним аргументом. Операции над списками, возвращающие новую последовательность, пытаются создать экземпляр фактического класса реализации. Для этого предполагается, что конструктор можно вызвать с одним параметром — объектом-последовательностью, используемым в качестве источника данных.

Если производный класс не должен соответствовать этому требованию, необходимо переопределить все специальные методы, поддерживаемые этим классом; сведения о методах, которые нужно предоставить в таком случае, см. в исходном коде.

Объекты UserString

Класс UserString служит оболочкой для строковых объектов. Необходимость в этом классе отчасти отпала благодаря возможности напрямую создавать подклассы str; однако с этим классом может быть проще работать, поскольку базовая строка доступна в виде атрибута.

class collections.UserString(seq)

Класс, имитирующий строковый объект. Содержимое экземпляра хранится в обычном строковом объекте, доступном через атрибут data экземпляров UserString. Изначально содержимому экземпляра присваивается копия seq. Аргумент seq может быть любым объектом, который можно преобразовать в строку с помощью встроенной функции str().

Помимо поддержки методов и операций строк, экземпляры UserString предоставляют следующий атрибут:

data

Настоящий объект str, используемый для хранения содержимого класса UserString.

Изменено в версии 3.5: Добавлены новые методы __getnewargs__, __rmod__, casefold, format_map, isprintable и maketrans.

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/collections.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API