Deque от Python: внедрение эффективных очередей и стеков
Оглавление
- Начало работы с deque от Python
- Эффективное удаление и добавление элементов
- Доступ к случайным элементам в deque
- Создание эффективных очередей с помощью deque
- Изучение других возможностей deque
- Использование функций deque, подобных последовательности
- Приведение deque Python в действие
- Заключение
Если вы часто работаете со списками в Python, то, вероятно, знаете, что они работают недостаточно быстро, когда вам нужно извлекать и добавлять элементы на их левом конце. Питона collections модуль предоставляет класс, называемый deque это специально разработано для обеспечения быстрой работы и экономии памяти способы добавления и извлечения элементов с обоих концов базовой структуры данных.
deque В Python - это низкоуровневая и высокооптимизированная двухсторонняя очередь, которая полезна для реализации элегантной, эффективной и питонической очереди и стеки, которые являются наиболее распространенными спископодобными типами данных в вычислительной технике.
В этом уроке вы узнаете:
- Как создать и использовать Python
dequeв вашем коде - Как эффективно добавлять и извлекать элементы с обоих концов
deque - Как использовать
dequeдля создания эффективных очередей и стеков - Когда стоит использовать
dequeвместоlist
Чтобы лучше разобраться в этих темах, вы должны знать основы работы с Python списками. Вам также будет полезно иметь общее представление о очередях и стеках.
Наконец, вы напишете несколько примеров, которые познакомят вас с некоторыми распространенными вариантами использования deque, который является одним из самых мощных типов данных в Python.
Скачать бесплатно: Ознакомьтесь с примером главы из книги "Приемы работы с Python: Книга", в которой показаны лучшие практики Python на простых примерах, которые вы можете подайте заявку немедленно, чтобы написать более красивый + Pythonic код.
Начало работы с Python deque
Добавление элементов в список Python и их перемещение из правого конца списка Python обычно являются эффективными операциями. Если вы используете Обозначение большой буквы О для временной сложности, то вы можете сказать, что они равны O(1). Однако, когда Python необходимо перераспределить память, чтобы увеличить базовый список для приема новых элементов, эти операции выполняются медленнее и могут стать O(n).
Кроме того, добавление и удаление элементов в левой части списка Python, как известно, являются неэффективными операциями с O(n) скорость.
Поскольку списки Python предоставляют обе операции с .append() и .pop(), их можно использовать как стеки и очереди. Однако проблемы с производительностью, с которыми вы сталкивались ранее, могут существенно повлиять на общую производительность ваших приложений.
Питоновские deque был ли первый тип данных добавлен в collections модуль вернемся к Python 2.4. Этот тип данных был специально разработан для преодоления проблем с эффективностью .append() и .pop() в Python list.
Deques - это типы данных, подобные последовательности, разработанные как обобщение стеков и очередей. Они поддерживают экономичные с точки зрения памяти и быстрые операции добавления и извлечения данных на обоих концах структуры данных.
Примечание: deque произносится как “колода”. Название расшифровывается как double-eнайдено что такоеue.
Операции добавления и извлечения на обоих концах объекта deque стабильны и одинаково эффективны, поскольку деквалификации реализованы в виде двусвязного списка. Кроме того, операции добавления и извлечения данных в deques также потокобезопасны и экономят память. Эти функции делают deques особенно полезными для создания пользовательских стеков и очередей в Python.
Списки добавлений также подходят для тех случаев, когда вам нужно сохранить список последних просмотренных элементов, поскольку вы можете ограничить максимальную длину ваших списков добавлений. Если вы сделаете это, то, как только список будет заполнен, он автоматически удалит элементы с одного конца, когда вы добавите новые элементы с противоположного конца.
Вот краткое изложение основных характеристик deque:
- Хранит элементы любого типа данных
- Является изменяемым типом данных
- Поддерживает операций с членством с помощью
inоператора - Поддерживает индексацию, как в
a_deque[i] - Не поддерживает нарезку, как в
a_deque[0:2] - Поддерживает встроенные функции, которые работают с последовательностями и повторяемыми объектами, такими как
len(),sorted(),reversed(), и другие - Не поддерживает сортировку на месте
- Поддерживает обычную и обратную итерацию
- Поддерживает маринование с
pickle - Обеспечивает быстрые, экономичные с точки зрения памяти и потокобезопасные операции извлечения и добавления на обоих концах
Создание deque экземпляров - это простой процесс. Вам просто нужно импортировать deque из collections и вызвать его с необязательным iterable в качестве аргумента:
>>> from collections import deque >>> # Create an empty deque >>> deque() deque([]) >>> # Use different iterables to create deques >>> deque((1, 2, 3, 4)) deque([1, 2, 3, 4]) >>> deque([1, 2, 3, 4]) deque([1, 2, 3, 4]) >>> deque(range(1, 5)) deque([1, 2, 3, 4]) >>> deque("abcd") deque(['a', 'b', 'c', 'd']) >>> numbers = {"one": 1, "two": 2, "three": 3, "four": 4} >>> deque(numbers.keys()) deque(['one', 'two', 'three', 'four']) >>> deque(numbers.values()) deque([1, 2, 3, 4]) >>> deque(numbers.items()) deque([('one', 1), ('two', 2), ('three', 3), ('four', 4)])предварительно> кодовый блок>Если вы создадите экземпляр
dequeбез указанияiterableв качестве аргумента, то получите пустое значение deque. Если вы предоставляете и вводитеiterable, тоdequeинициализирует новый экземпляр данными из него. Инициализация выполняется слева направо с использованиемdeque.append().Инициализатор
dequeпринимает следующие два необязательных аргумента:
iterableсодержит повторяющийся элемент, который предоставляет данные инициализации.maxlenсодержит целое число , которое определяет максимальную длину строки.Как упоминалось ранее, если вы не укажете
iterable, то получите пустой запрос deque. Если вы укажете значение вmaxlen,, то в вашем deque будет храниться толькоmaxlenэлементов.Наконец, вы также можете использовать неупорядоченные повторяющиеся элементы, такие как наборы, для инициализации ваших значений. В таких случаях у вас не будет заранее определенного порядка для товаров в окончательной заявке.
Эффективное отображение и добавление элементов
Самое важное различие между
dequeиlistзаключается в том, что первый позволяет выполнять эффективные операции добавления и извлечения на обоих концах последовательности. Классdequeреализует специальные методы.popleft()и.appendleft(), которые работают непосредственно с левым концом последовательности:>>> from collections import deque >>> numbers = deque([1, 2, 3, 4]) >>> numbers.popleft() 1 >>> numbers.popleft() 2 >>> numbers deque([3, 4]) >>> numbers.appendleft(2) >>> numbers.appendleft(1) >>> numbers deque([1, 2, 3, 4])предварительно> кодовый блок>Здесь вы используете
.popleft()и.appendleft()для удаления и добавления значений, соответственно, в левый конецnumbers. Эти методы специфичны для разработкиdeque, и вы не найдете их вlist.Точно так же, как
list,dequeтакже предоставляет.append()и.pop()методы для работы с правым концом последовательности. Однако.pop()ведет себя по-другому:>>> from collections import deque >>> numbers = deque([1, 2, 3, 4]) >>> numbers.pop() 4 >>> numbers.pop(0) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pop() takes no arguments (1 given)предварительно> кодовый блок>Здесь
.pop()удаляет и возвращает последнее значение в deque. Метод не принимает индекс в качестве аргумента, поэтому вы не можете использовать его для удаления произвольных элементов из ваших deques. Вы можете использовать его только для удаления и возврата самого правого элемента.Как вы узнали ранее,
dequeреализован в виде двусвязного списка. Таким образом, каждый элемент в данном списке содержит ссылку (указатель) на следующий и предыдущий элементы в последовательности.Двусвязные списки упрощают добавление и удаление элементов с обоих концов. Это возможно, потому что обновлять нужно только указатели. В результате обе операции имеют одинаковую производительность, O(1). Они также предсказуемы с точки зрения производительности, поскольку нет необходимости перераспределять память и перемещать существующие элементы для добавления новых.
Добавление и удаление элементов из левого конца обычного списка Python требует перемещения всех элементов, что в конечном итоге является операцией O(n). Кроме того, для добавления элементов в правый конец списка часто требуется, чтобы Python перераспределил память и скопировал текущие элементы в новую ячейку памяти. После этого он может добавлять новые элементы. Этот процесс занимает больше времени, и операция добавления изменяется с O(1) на O(n).
Рассмотрим следующие тесты производительности для добавления элементов в левый конец последовательности,
dequeпротивlist:# time_append.py from collections import deque from time import perf_counter TIMES = 10_000 a_list = [] a_deque = deque() def average_time(func, times): total = 0.0 for i in range(times): start = perf_counter() func(i) total += (perf_counter() - start) * 1e9 return total / times list_time = average_time(lambda i: a_list.insert(0, i), TIMES) deque_time = average_time(lambda i: a_deque.appendleft(i), TIMES) gain = list_time / deque_time print(f"list.insert() {list_time:.6} ns") print(f"deque.appendleft() {deque_time:.6} ns ({gain:.6}x faster)")предварительно> кодовый блок>В этом скрипте
average_time()вычисляет среднее время, которое занимает выполнение функции (func) из заданного числаtimes. Если вы запустите скрипт из командной строки, то получите следующий результат:$ python time_append.py list.insert() 3735.08 ns deque.appendleft() 238.889 ns (15.6352x faster)предварительно> кодовый блок>В этом конкретном примере
.appendleft()наdequeвыполняется в несколько раз быстрее, чем.insert()наlist. Обратите внимание, чтоdeque.appendleft()равно O(1), что означает, что время выполнения постоянно. Однакоlist.insert()в левом конце списка находится O(n), что означает, что время выполнения зависит от количества обрабатываемых элементов.В этом примере, если вы увеличите значение
TIMES, то вы получите более длительные измерения времени дляlist.insert(), но стабильные (постоянные) результаты дляdeque.appendleft(). Если вы хотите попробовать аналогичный тест производительности при выполнении pop-операций как для deques, так и для списков, то вы можете расширить приведенный ниже блок упражнений и сравнить свои результаты с результатами реального Python после того, как закончите.
В качестве упражнения вы можете изменить приведенный выше сценарий, установив время выполнения операций
deque.popleft()противlist.pop(0)и оценив их производительность.
Вот скрипт, который проверяет производительность операций
deque.popleft()иlist.pop(0):# time_pop.py from collections import deque from time import perf_counter TIMES = 10_000 a_list = [1] * TIMES a_deque = deque(a_list) def average_time(func, times): total = 0.0 for _ in range(times): start = perf_counter() func() total += (perf_counter() - start) * 1e9 return total / times list_time = average_time(lambda: a_list.pop(0), TIMES) deque_time = average_time(lambda: a_deque.popleft(), TIMES) gain = list_time / deque_time print(f"list.pop(0) {list_time:.6} ns") print(f"deque.popleft() {deque_time:.6} ns ({gain:.6}x faster)")предварительно> кодовый блок>Если вы запустите этот скрипт на своем компьютере, то получите результат, аналогичный следующему:
list.pop(0) 2002.08 ns deque.popleft() 326.454 ns (6.13282x faster)предварительно> кодовый блок>Опять же,
dequeработает быстрее, чемlist, когда дело доходит до удаления элементов из левого конца базовой последовательности. Попробуйте изменить значениеTIMESи посмотрите, что произойдет!Тип данных
dequeбыл разработан таким образом, чтобы гарантировать эффективность операций добавления и извлечения в любом конце последовательности. Он идеально подходит для решения задач, требующих реализации структур данных очереди и стека в Python.Доступ к случайным элементам в
dequeФункция
dequeв Python возвращает изменяемые последовательности, которые работают аналогично спискам. Помимо того, что deques позволяет эффективно добавлять и извлекать элементы с их концов, они предоставляют группу методов, похожих на списки, и другие операции, похожие на последовательности, для работы с элементами в произвольных местах. Вот некоторые из них:
Option Description .insert(i, value)Insert an item valueinto a deque at indexi..remove(value)Remove the first occurrence of value, raisingValueErrorif thevaluedoesn’t exist.a_deque[i]Retrieve the item at index ifrom a deque.del a_deque[i]Remove the item at index ifrom a deque.Вы можете использовать эти методы и приемчики для работы с элементами, расположенными в любом положении внутри объекта
deque. Вот как это сделать:>>> from collections import deque >>> letters = deque("abde") >>> letters.insert(2, "c") >>> letters deque(['a', 'b', 'c', 'd', 'e']) >>> letters.remove("d") >>> letters deque(['a', 'b', 'c', 'e']) >>> letters[1] 'b' >>> del letters[2] >>> letters deque(['a', 'b', 'e'])предварительно> кодовый блок>Здесь вы сначала вставляете
"c"вlettersв позиции2. Затем вы удаляете"d"из списка, используя.remove(). Ссылки также позволяют индексировать для доступа к элементам, которые вы используете здесь для доступа к"b"по индексу1. Наконец, вы можете использовать ключевое словоdel, чтобы удалить все существующие элементы из списка. Обратите внимание, что.remove()позволяет удалять элементы по значению, в то время какdelудаляет элементы по индексу.Несмотря на то, что объекты
dequeподдерживают индексацию, они не поддерживают нарезку. Другими словами, вы не можете извлечь фрагмент из существующего deque, используя синтаксис фрагментов,[start:stop:step], как это было бы с обычным списком :>>> from collections import deque >>> numbers = deque([1, 2, 3, 4, 5]) >>> numbers[1:3] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sequence index must be integer, not 'slice'предварительно> кодовый блок>Разделители поддерживают индексацию, но, что интересно, они не поддерживают нарезку. При попытке получить фрагмент из разделителя, вы получаете
TypeError. В общем случае выполнение разбивки на части связанного списка было бы неэффективным, поэтому эта операция недоступна.До сих пор вы видели, что
dequeочень похож наlist. Однако, в то время какlistоснован на массивах ,dequeоснован на двусвязном списке.За
dequeреализацией в виде двусвязного списка скрыты издержки: доступ, вставка и удаление произвольных элементов не являются эффективными операциями. Чтобы выполнить их, интерпретатор должен выполнить итерацию по списку, пока не доберется до нужного элемента. Таким образом, это O(n) вместо O(1) операций.Вот скрипт, который показывает, как ведут себя деки и списки, когда дело доходит до работы с произвольными элементами:
# time_random_access.py from collections import deque from time import perf_counter TIMES = 10_000 a_list = [1] * TIMES a_deque = deque(a_list) def average_time(func, times): total = 0.0 for _ in range(times): start = perf_counter() func() total += (perf_counter() - start) * 1e6 return total / times def time_it(sequence): middle = len(sequence) // 2 sequence.insert(middle, "middle") sequence[middle] sequence.remove("middle") del sequence[middle] list_time = average_time(lambda: time_it(a_list), TIMES) deque_time = average_time(lambda: time_it(a_deque), TIMES) gain = deque_time / list_time print(f"list {list_time:.6} μs ({gain:.6}x faster)") print(f"deque {deque_time:.6} μs")предварительно> кодовый блок>Этот скрипт выполняет вставку, удаление и доступ к элементам в середине строки и списка. Если вы запустите скрипт, то получите результат, который выглядит следующим образом:
$ python time_random_access.py list 63.8658 μs (1.44517x faster) deque 92.2968 μsпредварительно> кодовый блок>Списки не являются структурами данных с произвольным доступом, как списки. Поэтому доступ к элементам из середины списка менее эффективен, чем выполнение того же действия со списком. Главный вывод здесь заключается в том, что определения не всегда более эффективны, чем списки.
dequeВ Python оптимизирован для операций с любым концом последовательности, поэтому в этом отношении он неизменно лучше списков. С другой стороны, списки лучше подходят для операций с произвольным доступом и фиксированной длиной. Вот некоторые различия между deques и lists с точки зрения производительности:
Operation dequelistAccessing arbitrary items through indexing O(n) O(1) Popping and appending items on the left end O(1) O(n) Popping and appending items on the right end O(1) O(1) + reallocation Inserting and deleting items in the middle O(n) O(n) В случае списков,
.append()снижает производительность из-за перераспределения памяти, когда интерпретатору необходимо увеличить список, чтобы принимать новые элементы. Эта операция требует копирования всех текущих элементов в новую ячейку памяти, что существенно влияет на производительность.Это краткое изложение может помочь вам выбрать подходящий тип данных для конкретной задачи. Однако, прежде чем переходить от списков к декодированию, обязательно профилируйте свой код. У обоих есть свои сильные стороны в работе.
Построение эффективных очередей С помощью
dequeКак вы уже узнали,
dequeреализована в виде двусторонней очереди, которая представляет собой обобщение стеков и очередей. В этом разделе вы узнаете, как использоватьdequeдля реализации вашей собственной очереди абстрактных типов данных (ADT) на низком уровне элегантным, эффективным и основанным на Python способом.Примечание: В стандартной библиотеке Python вы найдете
queue. Этот модуль реализует очереди с несколькими производителями и несколькими потребителями, которые позволяют безопасно обмениваться информацией между несколькими потоками.Если вы работаете с очередями, то предпочитайте использовать эти высокоуровневые абстракции, а не
deque, если только вы не реализуете свою собственную структуру данных.Очереди - это коллекции элементов. Вы можете изменять очереди, добавляя элементы с одного конца и удаляя элементы с противоположного конца.
Очереди управляют своими элементами в режиме Первый вход/первый выход. ( FIFO) мода. Они работают как конвейер, в который вы помещаете новые элементы на одном конце конвейера и извлекаете старые элементы с другого конца. Добавление элемента в один конец очереди называется операцией постановка в очередь. Удаление элемента с другого конца называется Удалением из очереди.
Чтобы лучше понять очереди, возьмите в качестве примера свой любимый ресторан. В ресторане есть очередь из людей, ожидающих, когда им закажут еду. Как правило, в конце очереди стоит тот, кто приходит последним. Человек, стоящий в начале очереди, покинет ее, как только освободится столик.
Вот как вы можете эмулировать процесс, используя простой объект
deque:>>> from collections import deque >>> customers = deque() >>> # People arriving >>> customers.append("Jane") >>> customers.append("John") >>> customers.append("Linda") >>> customers deque(['Jane', 'John', 'Linda']) >>> # People getting tables >>> customers.popleft() 'Jane' >>> customers.popleft() 'John' >>> customers.popleft() 'Linda' >>> # No people in the queue >>> customers.popleft() Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: pop from an empty dequeпредварительно> кодовый блок>Здесь вы сначала создаете пустой объект
deque, который представляет очередь людей, прибывающих в ресторан. Чтобы поместить пользователя в очередь, вы используете.append(),, который добавляет отдельные элементы в правый конец. Чтобы удалить пользователя из очереди, вы используете.popleft(),, который удаляет и возвращает отдельные элементы в левой части списка.Круто! Ваше моделирование очереди работает! Однако, поскольку
dequeявляется обобщением, его API не соответствует типичному API очереди. Например, вместо.enqueue()у вас есть.append(). У вас также есть.popleft()вместо.dequeue(). Кроме того,dequeпредоставляет несколько других операций, которые могут не соответствовать вашим конкретным потребностям.Хорошей новостью является то, что вы можете создавать пользовательские классы очередей с нужной вам функциональностью и ничем иным. Для этого вы можете использовать deque для хранения данных и обеспечения желаемой функциональности в ваших пользовательских очередях. Вы можете рассматривать это как реализацию шаблона проектирования адаптера , в котором вы преобразуете интерфейс deque во что-то, что больше похоже на интерфейс очереди.
Например, предположим, что вам нужен пользовательский абстрактный тип данных очереди, который предоставляет только следующие функции:
- Размещение элементов в очереди
- Удаление элементов из очереди
- Возвращает длину очереди
- Поддерживает проверку членства
- Поддерживает обычную и обратную итерацию
- Предоставление удобного для пользователя строкового представления
В этом случае вы можете написать класс
Queue, который выглядит следующим образом:# custom_queue.py from collections import deque class Queue: def __init__(self): self._items = deque() def enqueue(self, item): self._items.append(item) def dequeue(self): try: return self._items.popleft() except IndexError: raise IndexError("dequeue from an empty queue") from None def __len__(self): return len(self._items) def __contains__(self, item): return item in self._items def __iter__(self): yield from self._items def __reversed__(self): yield from reversed(self._items) def __repr__(self): return f"Queue({list(self._items)})"предварительно> кодовый блок>Здесь
._itemsсодержитdequeобъект, который позволяет хранить элементы в очереди и манипулировать ими.Queueреализует.enqueue()с помощьюdeque.append()чтобы добавить элементы в конец очереди. Он также реализует.dequeue()с помощьюdeque.popleft()для эффективного удаления элементов из начала очереди.Специальные методы поддерживают следующие функции:
Method Support .__len__()Length with len().__contains__()Membership tests with in.__iter__()Normal iteration .__reversed__()Reverse iteration .__repr__()String representation В идеале,
.__repr__()должна возвращать строку, представляющую допустимое выражение Python. Это выражение позволит вам однозначно воссоздать объект с тем же значением.Однако в приведенном выше примере цель состоит в том, чтобы использовать возвращаемое методом значение для корректного отображения объекта в интерактивной оболочке. Вы можете создать возможность создавать экземпляры
Queueиз этого конкретного строкового представления, приняв итерацию инициализации в качестве аргумента для.__init__()и создавая экземпляры на его основе.С этими последними дополнениями ваш класс
Queueзавершен. Чтобы использовать этот класс в своем коде, вы можете сделать что-то вроде следующего:>>> from custom_queue import Queue >>> numbers = Queue() >>> numbers Queue([]) >>> # Enqueue items >>> for number in range(1, 5): ... numbers.enqueue(number) ... >>> numbers Queue([1, 2, 3, 4]) >>> # Support len() >>> len(numbers) 4 >>> # Support membership tests >>> 2 in numbers True >>> 10 in numbers False >>> # Normal iteration >>> for number in numbers: ... print(f"Number: {number}") ... Number: 1 Number: 2 Number: 3 Number: 4предварительно> кодовый блок>В качестве упражнения вы можете протестировать оставшиеся функции и реализовать другие возможности, такие как поддержка тестов на равенство, удаление случайных элементов и доступ к ним и многое другое. Попробуйте!
Изучение других возможностей
dequeВ дополнение к функциям, которые вы уже видели,
dequeтакже предоставляет другие методы и атрибуты, характерные для их внутренней структуры. Они добавляют новые полезные функции к этому универсальному типу данных.В этом разделе вы узнаете о других методах и атрибутах, предоставляемых deques, о том, как они работают и как их использовать в вашем коде.
Ограничение максимального количества элементов:
maxlenОдной из наиболее полезных функций
dequeявляется возможность указать максимальную длину заданного значения, используя аргументmaxlen, когда вы создание экземпляра класса.Если вы укажете значение
maxlen, то в вашем deque будет храниться толькоmaxlenэлементов. В этом случае у вас есть ограниченный вывод. Как только ограниченный список заполняется указанным количеством элементов, добавление нового элемента на обоих концах автоматически удаляет элемент на противоположном конце:>>> from collections import deque >>> four_numbers = deque([0, 1, 2, 3, 4], maxlen=4) # Discard 0 >>> four_numbers deque([1, 2, 3, 4], maxlen=4) >>> four_numbers.append(5) # Automatically remove 1 >>> four_numbers deque([2, 3, 4, 5], maxlen=4) >>> four_numbers.append(6) # Automatically remove 2 >>> four_numbers deque([3, 4, 5, 6], maxlen=4) >>> four_numbers.appendleft(2) # Automatically remove 6 >>> four_numbers deque([2, 3, 4, 5], maxlen=4) >>> four_numbers.appendleft(1) # Automatically remove 5 >>> four_numbers deque([1, 2, 3, 4], maxlen=4) >>> four_numbers.maxlen 4предварительно> кодовый блок>Если количество элементов во входной итерируемой переменной больше, чем
maxlen, тоdequeотбрасывает крайние левые элементы (0в примере). Как только список заполнится, добавление элемента на любом конце автоматически приведет к удалению элемента на другом конце.Обратите внимание, что если вы не укажете значение
maxlen, то по умолчанию оно будет равноNone,, и количество элементов может увеличиться до произвольного числа .Возможность ограничить максимальное количество элементов позволяет использовать deques для отслеживания последних элементов в заданной последовательности объектов или событий. Например, вы можете отслеживать последние пять транзакций по банковскому счету, последние десять открытых текстовых файлов в редакторе, последние пять страниц в браузере и многое другое.
Обратите внимание, что
maxlenдоступен в качестве атрибута только для чтения в ваших deques, что позволяет вам проверить, заполнен ли deques, как вdeque.maxlen == len(deque).Наконец, вы можете установить для
maxlenлюбое положительное целое число, представляющее максимальное количество элементов, которые вы хотите сохранить в определенном хранилище. Если вы введете отрицательное значение вmaxlen, то получитеValueError.Вращение элементов:
.rotate()Еще одной интересной особенностью deques является возможность поворачивать их элементы, вызывая
.rotate()для непустого deques. Этот метод принимает целое числоnв качестве аргумента и поворачивает элементы наnшагов вправо. Другими словами, он перемещаетnэлементов с правого конца на левый по кругу.Значение по умолчанию для
nравно1. Если вы укажете отрицательное значение дляn, то поворот будет влево:>>> from collections import deque >>> ordinals = deque(["first", "second", "third"]) >>> # Rotate items to the right >>> ordinals.rotate() >>> ordinals deque(['third', 'first', 'second']) >>> ordinals.rotate(2) >>> ordinals deque(['first', 'second', 'third']) >>> # Rotate items to the left >>> ordinals.rotate(-2) >>> ordinals deque(['third', 'first', 'second']) >>> ordinals.rotate(-1) >>> ordinals deque(['first', 'second', 'third'])предварительно> кодовый блок>В этих примерах вы поворачиваете
ordinalsнесколько раз, используя.rotate()с разными значениямиn. Если вы вызываете.rotate()без аргумента, то он использует значение по умолчаниюnи поворачивает позицию deque1вправо. Вызов метода с отрицательным значениемnпозволяет поворачивать элементы влево.Добавление сразу нескольких элементов:
.extendleft()Как и в обычных списках, в deques есть метод
.extend(), который позволяет добавлять несколько элементов в правый конец списка, используяiterableв качестве аргумента. Кроме того, в deque есть метод с именемextendleft(),, который принимаетiterableв качестве аргумента и добавляет свои элементы в левый конец целевого deque за один раз:>>> from collections import deque >>> numbers = deque([1, 2]) >>> # Extend to the right >>> numbers.extend([3, 4, 5]) >>> numbers deque([1, 2, 3, 4, 5]) >>> # Extend to the left >>> numbers.extendleft([-1, -2, -3, -4, -5]) >>> numbers deque([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])предварительно> кодовый блок>Вызов
.extendleft()с помощьюiterableрасширяет целевой диапазон влево. Внутри.extendleft()выполняется серия отдельных.appendleft()операций, которые обрабатывают вводимые данные, повторяющиеся слева направо. В результате элементы добавляются в обратном порядке в левый конец целевого списка.Используя функции, подобные последовательности, в
dequeПоскольку deques являются изменяемыми последовательностями, они реализуют почти все методы и операции, которые являются общими для последовательностей и изменяемых последовательностей. К настоящему времени вы узнали о некоторых из этих методов и операций, таких как
.insert(), индексация, тесты на членство и многое другое.Вот несколько примеров других действий, которые вы можете выполнить с объектами
deque:>>> from collections import deque >>> numbers = deque([1, 2, 2, 3, 4, 4, 5]) >>> # Concatenation >>> numbers + deque([6, 7, 8]) deque([1, 2, 2, 3, 4, 4, 5, 6, 7, 8]) >>> # Repetition >>> numbers * 2 deque([1, 2, 2, 3, 4, 4, 5, 1, 2, 2, 3, 4, 4, 5]) >>> # Common sequence methods >>> numbers = deque([1, 2, 2, 3, 4, 4, 5]) >>> numbers.index(2) 1 >>> numbers.count(4) 2 >>> # Common mutable sequence methods >>> numbers.reverse() >>> numbers deque([5, 4, 4, 3, 2, 2, 1]) >>> numbers.clear() >>> numbers deque([])предварительно> кодовый блок>Вы можете использовать оператор сложения (
+) для объединения двух существующих значений. С другой стороны, оператор умножения (*) возвращает новое значение, эквивалентное повторению исходного значения столько раз, сколько вы хотите.Что касается других методов определения последовательности, то в следующей таблице приведена краткая информация:
Method Description .clear()Remove all the elements from a deque. .copy()Create a shallow copy of a deque. .count(value)Count the number times valueappears in a deque..index(value)Return the position of valuein the deque..reverse()Reverse the elements of the deque in place and then return None.Здесь
.index()также может принимать два необязательных аргумента:startиstop. Они позволяют ограничить поиск теми элементами, которые находятся вstartи передstopили после них. Метод выдает запросValueErrorеслиvalueне отображается в имеющемся списке.В отличие от списков, в deques нет метода
.sort()для сортировки последовательности на месте. Это связано с тем, что сортировка связанного списка была бы неэффективной операцией. Если вам когда-нибудь понадобится отсортировать данные, вы все равно можете использоватьsorted().Приведение в действие
dequeиз PythonВы можете использовать deques в большом количестве вариантов использования, например, для реализации очередей, стеков и циклических буферов. Вы также можете использовать их для ведения журнала отмен и повторов, постановки в очередь входящих запросов к веб-службе , ведения списка недавно открытых файлов и веб-сайтов, безопасного обмена данными между несколькими потоками и многого другого.
В следующих разделах вы создадите несколько небольших примеров, которые помогут вам лучше понять, как использовать deques в вашем коде.
Ведение истории страниц
Наличие
maxlenдля ограничения максимального количества элементов делаетdequeподходящим для решения нескольких задач. Например, предположим, что вы создаете приложение, которое собирает данные из поисковых систем и сайтов социальных сетей. В какой-то момент вам потребуется отслеживать три последних сайта, с которых ваше приложение запрашивало данные.Чтобы решить эту проблему, вы можете использовать deque с
maxlenиз3:>>> from collections import deque >>> sites = ( ... "google.com", ... "yahoo.com", ... "bing.com" ... ) >>> pages = deque(maxlen=3) >>> pages.maxlen 3 >>> for site in sites: ... pages.appendleft(site) ... >>> pages deque(['bing.com', 'yahoo.com', 'google.com'], maxlen=3) >>> pages.appendleft("facebook.com") >>> pages deque(['facebook.com', 'bing.com', 'yahoo.com'], maxlen=3) >>> pages.appendleft("twitter.com") >>> pages deque(['twitter.com', 'facebook.com', 'bing.com'], maxlen=3)предварительно> кодовый блок>В этом примере
pagesсодержит список трех последних сайтов, которые посетило ваше приложение. Как толькоpagesзаполнится, добавление нового сайта в конец списка автоматически приведет к удалению сайта в противоположном конце. Таким образом, в вашем списке будут обновлены три последних сайта, которые вы использовали.Обратите внимание, что вы можете установить значение
maxlenв любое положительное целое число, представляющее количество элементов, которые будут храниться в имеющемся хранилище. Например, если вы хотите сохранить список из десяти сайтов, то вы можете установить дляmaxlenзначение10.Обмен данными между Потоками
Python
dequeтакже полезен при написании многопоточных приложений, как описано Раймондом Хеттингером, разработчик ядра Python и создательdequeи модуляcollections:Операции deque
.append(),.appendleft(),.pop(),.popleft(), иlen(d)в CPython являются потокобезопасными. (Источник)Благодаря этому вы можете безопасно добавлять и удалять данные с обоих концов deque одновременно из отдельных потоков без риска повреждения данных или других связанных с этим проблем.
Чтобы попробовать, как
dequeработает в многопоточном приложении, запустите свой любимый редактор кода, создайте новый скрипт с именемthreads.pyи добавьте следующий код к нему:# threads.py import logging import random import threading import time from collections import deque logging.basicConfig(level=logging.INFO, format="%(message)s") def wait_seconds(mins, maxs): time.sleep(mins + random.random() * (maxs - mins)) def produce(queue, size): while True: if len(queue) < size: value = random.randint(0, 9) queue.append(value) logging.info("Produced: %d -> %s", value, str(queue)) else: logging.info("Queue is saturated") wait_seconds(0.1, 0.5) def consume(queue): while True: try: value = queue.popleft() except IndexError: logging.info("Queue is empty") else: logging.info("Consumed: %d -> %s", value, str(queue)) wait_seconds(0.2, 0.7) logging.info("Starting Threads...\n") logging.info("Press Ctrl+C to interrupt the execution\n") shared_queue = deque() threading.Thread(target=produce, args=(shared_queue, 10)).start() threading.Thread(target=consume, args=(shared_queue,)).start()предварительно> кодовый блок>Здесь
produce()принимаетqueueиsizeв качестве аргументов. Затем он используетrandom.randint()в циклеwhileдля непрерывного создания случайных чисел и сохраните их в глобальном списке под названиемshared_queue. Поскольку добавление элементов в deque является потокобезопасной операцией, вам не нужно использовать блокировку для защиты общих данных от других потоков.Вспомогательная функция
wait_seconds()имитирует, что иproduce(), иconsume()представляют собой длительные операции. Он возвращает случайное значение времени ожидания в заданном диапазоне секунд,minsиmaxs.В
consume()вы вызываете.popleft()внутри цикла для систематического извлечения и удаления данных изshared_queue. Вы завершаете вызов.popleft()в инструкцииtry...exceptдля обработки тех случаев, когда общая очередь пуста.Обратите внимание, что, хотя вы определили
shared_queueв глобальном пространстве имен, вы получаете к нему доступ через локальные переменные внутриproduce()иconsume(). Прямой доступ к глобальной переменной был бы более проблематичным и определенно не лучшим решением.Последние две строки скрипта создают и запускают отдельные потоки для одновременного выполнения
produce()иconsume(). Если вы запустите скрипт из командной строки, то получите результат, похожий на следующий:$ python threads.py Starting Threads... Press Ctrl+C to interrupt the execution Produced: 1 -> deque([1]) Consumed: 1 -> deque([]) Queue is empty Produced: 3 -> deque([3]) Produced: 0 -> deque([3, 0]) Consumed: 3 -> deque([0]) Consumed: 0 -> deque([]) Produced: 1 -> deque([1]) Produced: 0 -> deque([1, 0]) ...предварительно> кодовый блок>Поток-производитель добавляет числа в правый конец общего списка, в то время как поток-потребитель использует числа из левого конца. Чтобы прервать выполнение скрипта, вы можете нажать Ctrl+C на клавиатуре.
Наконец, вы можете немного поиграть с временным интервалом внутри
produce()иconsume(). Измените передаваемые значения наwait_seconds()и посмотрите, как ведет себя программа, когда производитель работает медленнее, чем потребитель, и наоборот.Эмуляция команды
tailПоследний пример, который вы закодируете здесь, эмулирует
tailкоманду, которая доступна в Unix и Unix-подобные операционные системы. Команда принимает путь к файлу в командной строке и выводит последние десять строк этого файла в стандартный системный вывод. Вы можете настроить количество строк, которое вам нужноtailдля печати, с помощью опции-n,--lines.Вот небольшая функция Python, которая эмулирует основную функциональность
tail:>>> from collections import deque >>> def tail(filename, lines=10): ... try: ... with open(filename) as file: ... return deque(file, lines) ... except OSError as error: ... print(f'Opening file "{filename}" failed with error: {error}') ...предварительно> кодовый блок>Здесь вы определяете
tail(). Первый аргумент,filename, содержит путь к целевому файлу в виде строки. Второй аргумент,lines, представляет количество строк, которые вы хотите извлечь из конца целевого файла. Обратите внимание, чтоlinesпо умолчанию имеет значение10для имитации поведения по умолчаниюtail.Примечание: Первоначальная идея для этого примера взята из документации по Python на
deque. Ознакомьтесь с разделом, посвященнымdequeрецептам, для получения дополнительных примеров.Значение deque в выделенной строке может содержать только то количество элементов, которое вы передаете в
lines. Это гарантирует, что вы получите желаемое количество строк из конца входного файла.Как вы видели ранее, когда вы создаете ограниченный deque и инициализируете его с помощью iterable, он содержит больше элементов, чем разрешено (
maxlen), конструкторdequeотбрасывает все крайние левые элементы во входных данных. Из-за этого вы в конечном итоге получаете последниеmaxlenстроки целевого файла.Заключение
Очереди и стеки обычно используются абстрактные типы данных в программировании. Обычно для них требуются эффективные операции pop и append на обоих концах базовой структуры данных. Модуль Python
collectionsпредоставляет тип данных под названиемdeque, который специально разработан для быстрого и экономичного использования памяти операций добавления и извлечения данных с обеих сторон.С помощью
dequeвы можете создавать свои собственные очереди и стеки на низком уровне элегантным, эффективным и основанным на Python способом.В этом руководстве вы узнали, как:
- Создайте и используйте Python
dequeв своем коде- Эффективно добавлять и извлекать элементов из обоих концов последовательности с помощью
deque- Используйте
dequeдля создания эффективных очередей и стеков в Python- Решите, когда использовать
dequeвместоlistВ этом руководстве вы также написали несколько примеров, которые помогли вам приблизиться к некоторым распространенным вариантам использования
<статус завершения article-slug="python-deque" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-deque/bookmark/" url статуса завершения data-api-article-url="/api/v1/articles/python-deque/завершение_статуса/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0apython's deque: внедрение эффективных очередей и стеков" email-subject="Статья о Python для вас" twitter-text="Интересная статья #Python статья от @realpython:" url="https://realpython.com/python-deque /" url-title="Deque в Python: реализация эффективных очередей и стеков"> кнопка поделиться> Back to Topdequeв Python.