Spec-Zone.ru › Python 3.11

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

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

Объекты ChainMap

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

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

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

class collections.ChainMap(*maps)

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

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

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

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

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

maps

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

new_child(m=None, **kwargs)

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

Изменено в версии 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

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

>>> # 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 будет пустым.

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

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

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

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

append(x)

Добавляет x справа от дека.

appendleft(x)

Добавляет x слева от дека.

clear()

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

copy()

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

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

count(x)

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

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

extend(iterable)

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

extendleft(iterable)

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

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

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

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

insert(i, x)

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

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

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

pop()

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

popleft()

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

remove(value)

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

reverse()

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

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

rotate(n=1)

Сдвигает элементы дека на n позиций вправо. Если n отрицательно, сдвиг выполняется влево.

Если дек не пуст, сдвиг на одну позицию вправо эквивалентен d.appendleft(d.pop()), а сдвиг на одну позицию влево — d.append(d.popleft()).

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

maxlen

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

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

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

Начиная с версии 3.5 деки поддерживают __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

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

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

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

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

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. Значения выдаются из активного итератора, находящегося на позиции 0. Если итератор исчерпан, его можно удалить с помощью 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(), чтобы переместить нужный элемент к левому краю дека. Удалите старые элементы с помощью 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, аргументом которого является key.

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

Если вызов 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 может имитировать метод od.popitem(last=True) объекта OrderedDict с помощью d.popitem(), который гарантированно удаляет крайний правый (последний) элемент.

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

  • У OrderedDict есть метод move_to_end() для эффективного перемещения элемента в один из концов.

    Обычный dict может имитировать метод od.move_to_end(k, last=True) объекта OrderedDict с помощью 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)

Перемещает существующий key в один из концов упорядоченного словаря. Если last равно true (значение по умолчанию), элемент перемещается в правый конец; если last равно false — в начало. Если key не существует, возбуждается исключение 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 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.11/library/collections.html

Spec-Zone.ru

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