From 30bf1eb48c72b0e7b613f4673f55132a874eff2a Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Tue, 28 Oct 2025 14:00:39 +0100 Subject: [PATCH 1/3] Sample code for the article on deque --- python-deque/README.md | 3 ++ python-deque/custom_queue.py | 30 ++++++++++++++ python-deque/page.py | 18 ++++++++ python-deque/producer_consumer.py | 66 ++++++++++++++++++++++++++++++ python-deque/rotate.py | 21 ++++++++++ python-deque/tail.py | 9 ++++ python-deque/threads.py | 42 +++++++++++++++++++ python-deque/time_append.py | 23 +++++++++++ python-deque/time_pop.py | 23 +++++++++++ python-deque/time_random_access.py | 31 ++++++++++++++ 10 files changed, 266 insertions(+) create mode 100644 python-deque/README.md create mode 100644 python-deque/custom_queue.py create mode 100644 python-deque/page.py create mode 100644 python-deque/producer_consumer.py create mode 100644 python-deque/rotate.py create mode 100644 python-deque/tail.py create mode 100644 python-deque/threads.py create mode 100644 python-deque/time_append.py create mode 100644 python-deque/time_pop.py create mode 100644 python-deque/time_random_access.py diff --git a/python-deque/README.md b/python-deque/README.md new file mode 100644 index 0000000000..8e8cb1eb26 --- /dev/null +++ b/python-deque/README.md @@ -0,0 +1,3 @@ +# Python's deque: Implement Efficient Queues and Stacks + +This folder provides the code examples for the Real Python tutorial [Python's deque: Implement Efficient Queues and Stacks](https://realpython.com/python-deque/). diff --git a/python-deque/custom_queue.py b/python-deque/custom_queue.py new file mode 100644 index 0000000000..30c73e5073 --- /dev/null +++ b/python-deque/custom_queue.py @@ -0,0 +1,30 @@ +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 __str__(self): + return f"Queue({list(self._items)})" + + def __iter__(self): + yield from self._items + + def __reversed__(self): + yield from reversed(self._items) diff --git a/python-deque/page.py b/python-deque/page.py new file mode 100644 index 0000000000..bd2adb6199 --- /dev/null +++ b/python-deque/page.py @@ -0,0 +1,18 @@ +from collections import deque + +doc_history = deque(maxlen=3) + +urls = ("https://google.com", "https://yahoo.com", "https://www.bing.com") + +for url in urls: + doc_history.appendleft(url) + +print(doc_history) + +doc_history.appendleft("https://youtube.com") + +print(doc_history) + +doc_history.appendleft("https://facebook.com") + +print(doc_history) diff --git a/python-deque/producer_consumer.py b/python-deque/producer_consumer.py new file mode 100644 index 0000000000..d157186c8e --- /dev/null +++ b/python-deque/producer_consumer.py @@ -0,0 +1,66 @@ +import collections +import logging +import random +import threading +import time + +logging.basicConfig(level=logging.INFO, format="%(threadName)s %(message)s") + + +class SynchronizedBuffer: + def __init__(self, capacity): + self.values = collections.deque(maxlen=capacity) + self.lock = threading.RLock() + self.consumed = threading.Condition(self.lock) + self.produced = threading.Condition(self.lock) + + def __repr__(self): + return repr(list(self.values)) + + @property + def empty(self): + return len(self.values) == 0 + + @property + def full(self): + return len(self.values) == self.values.maxlen + + def put(self, value): + with self.lock: + self.consumed.wait_for(lambda: not self.full) + self.values.append(value) + self.produced.notify() + + def get(self): + with self.lock: + self.produced.wait_for(lambda: not self.empty) + try: + return self.values.popleft() + finally: + self.consumed.notify() + + +def producer(buffer): + while True: + value = random.randint(1, 10) + buffer.put(value) + logging.info("produced %d: %s", value, buffer) + time.sleep(random.random()) + + +def consumer(buffer): + while True: + value = buffer.get() + logging.info("consumed %d: %s", value, buffer) + time.sleep(random.random()) + + +if __name__ == "__main__": + + buffer = SynchronizedBuffer(5) + + for _ in range(3): + threading.Thread(target=producer, args=(buffer,)).start() + + for _ in range(2): + threading.Thread(target=consumer, args=(buffer,)).start() diff --git a/python-deque/rotate.py b/python-deque/rotate.py new file mode 100644 index 0000000000..6c975a9aa7 --- /dev/null +++ b/python-deque/rotate.py @@ -0,0 +1,21 @@ +from collections import deque + + +def slice_deque(deck, *, start, stop): + slice = deque() + temp = deque() + for i in range(stop, start, -1): + deck.rotate(i) + item = deck.popleft() + slice.appendleft(item) + temp.append(item) + deck.extend(temp) + # for i in range(start, stop): + # deck.rotate(-i) + + return slice + + +d = deque([1, 2, 3, 4, 5]) +print(slice_deque(d, start=0, stop=3)) +print(d) diff --git a/python-deque/tail.py b/python-deque/tail.py new file mode 100644 index 0000000000..782cecc757 --- /dev/null +++ b/python-deque/tail.py @@ -0,0 +1,9 @@ +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}') diff --git a/python-deque/threads.py b/python-deque/threads.py new file mode 100644 index 0000000000..ecf4782fa3 --- /dev/null +++ b/python-deque/threads.py @@ -0,0 +1,42 @@ +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() diff --git a/python-deque/time_append.py b/python-deque/time_append.py new file mode 100644 index 0000000000..e4e8a78a3d --- /dev/null +++ b/python-deque/time_append.py @@ -0,0 +1,23 @@ +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)") diff --git a/python-deque/time_pop.py b/python-deque/time_pop.py new file mode 100644 index 0000000000..29a3ebfefb --- /dev/null +++ b/python-deque/time_pop.py @@ -0,0 +1,23 @@ +from collections import deque +from time import perf_counter + +TIMES = 10000 +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() {list_time:.6} ns") +print(f"deque.popleft() {deque_time:.6} ns ({gain:.6}x faster)") diff --git a/python-deque/time_random_access.py b/python-deque/time_random_access.py new file mode 100644 index 0000000000..f6106c8d6e --- /dev/null +++ b/python-deque/time_random_access.py @@ -0,0 +1,31 @@ +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") From b6c3ebbe7ba7c264e66dd333ff2c25a11041f128 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Tue, 28 Oct 2025 14:03:29 +0100 Subject: [PATCH 2/3] Fix formatting --- python-deque/producer_consumer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python-deque/producer_consumer.py b/python-deque/producer_consumer.py index d157186c8e..44864b6390 100644 --- a/python-deque/producer_consumer.py +++ b/python-deque/producer_consumer.py @@ -4,7 +4,10 @@ import threading import time -logging.basicConfig(level=logging.INFO, format="%(threadName)s %(message)s") +logging.basicConfig( + level=logging.INFO, + format="%(threadName)s %(message)s", +) class SynchronizedBuffer: From 640f8e31651de153dc765e133d536f54a74265a7 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Tue, 28 Oct 2025 14:06:03 +0100 Subject: [PATCH 3/3] Update code --- python-deque/producer_consumer.py | 77 ++++++++++--------------------- 1 file changed, 25 insertions(+), 52 deletions(-) diff --git a/python-deque/producer_consumer.py b/python-deque/producer_consumer.py index 44864b6390..ecf4782fa3 100644 --- a/python-deque/producer_consumer.py +++ b/python-deque/producer_consumer.py @@ -1,69 +1,42 @@ -import collections import logging import random import threading import time +from collections import deque -logging.basicConfig( - level=logging.INFO, - format="%(threadName)s %(message)s", -) +logging.basicConfig(level=logging.INFO, format="%(message)s") -class SynchronizedBuffer: - def __init__(self, capacity): - self.values = collections.deque(maxlen=capacity) - self.lock = threading.RLock() - self.consumed = threading.Condition(self.lock) - self.produced = threading.Condition(self.lock) +def wait_seconds(mins, maxs): + time.sleep(mins + random.random() * (maxs - mins)) - def __repr__(self): - return repr(list(self.values)) - @property - def empty(self): - return len(self.values) == 0 - - @property - def full(self): - return len(self.values) == self.values.maxlen - - def put(self, value): - with self.lock: - self.consumed.wait_for(lambda: not self.full) - self.values.append(value) - self.produced.notify() - - def get(self): - with self.lock: - self.produced.wait_for(lambda: not self.empty) - try: - return self.values.popleft() - finally: - self.consumed.notify() - - -def producer(buffer): +def produce(queue, size): while True: - value = random.randint(1, 10) - buffer.put(value) - logging.info("produced %d: %s", value, buffer) - time.sleep(random.random()) + 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 consumer(buffer): +def consume(queue): while True: - value = buffer.get() - logging.info("consumed %d: %s", value, buffer) - time.sleep(random.random()) - + 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) -if __name__ == "__main__": - buffer = SynchronizedBuffer(5) +logging.info("Starting Threads...\n") +logging.info("Press Ctrl+C to interrupt the execution\n") - for _ in range(3): - threading.Thread(target=producer, args=(buffer,)).start() +shared_queue = deque() - for _ in range(2): - threading.Thread(target=consumer, args=(buffer,)).start() +threading.Thread(target=produce, args=(shared_queue, 10)).start() +threading.Thread(target=consume, args=(shared_queue,)).start()