LoginSignup
0
1

More than 3 years have passed since last update.

標準ライブラリを継承してQueueの平均値を求める

Last updated at Posted at 2019-12-31

はじめに

以前、なにかでQueueの値の平均値が求めたいということがありました(なんでそれが必要になったかは覚えていないですが)。
そのときは以下のコードでごり押しして平均値を求めていました。

queue_test.py
import queue


def queue_ave(q):
    queue_list = []
    while not q.empty():
        queue_list.append(q.get())
    for value in queue_list:  # stackの場合はreversed(queue_list)に変更
        q.put(value)
    return sum(queue_list) / len(queue_list)


# 動作確認
q = queue.Queue()
q.put(1)
q.put(2)
q.put(6)
q.put(8)
print(queue_ave(q))  # 4.25

このコード(関数)の流れとしては、

  1. Queueからすべて取り出してそれをListに収める
  2. 逆にListからQueueに戻す
  3. Listの平均値を戻り値に渡す

といったものとなってます。
確かにこれでもQueueの平均値は計算できます。
しかし、当然ですが無駄にputとgetを繰り返しているため計算コストがかかり、効率はとても悪いです。

標準ライブラリから継承する

そこで別のアプローチとして、標準ライブラリのqueueからQueueクラスを継承して、カスタマイズしたクラスを作成するといった方法をしてみます。

そのために、Queueクラスでは値をどのように管理しているかを確認します。
エディタによると思いますが、ソースコードは確認したいクラスを "Ctrl+クリック" で見れます。

queue.py
class Queue:
    '''Create a queue object with a given maximum size.

    If maxsize is <= 0, the queue size is infinite.
    '''

    # ~ 省略 ~

    def put(self, item, block=True, timeout=None):
        '''Put an item into the queue.

        If optional args 'block' is true and 'timeout' is None (the default),
        block if necessary until a free slot is available. If 'timeout' is
        a non-negative number, it blocks at most 'timeout' seconds and raises
        the Full exception if no free slot was available within that time.
        Otherwise ('block' is false), put an item on the queue if a free slot
        is immediately available, else raise the Full exception ('timeout'
        is ignored in that case).
        '''
        with self.not_full:
            if self.maxsize > 0:
                if not block:
                    if self._qsize() >= self.maxsize:
                        raise Full
                elif timeout is None:
                    while self._qsize() >= self.maxsize:
                        self.not_full.wait()
                elif timeout < 0:
                    raise ValueError("'timeout' must be a non-negative number")
                else:
                    endtime = time() + timeout
                    while self._qsize() >= self.maxsize:
                        remaining = endtime - time()
                        if remaining <= 0.0:
                            raise Full
                        self.not_full.wait(remaining)
            self._put(item)
            self.unfinished_tasks += 1
            self.not_empty.notify()

putメソッドを見ると色々と書いてありますが、なにやら_putメソッドで値の格納をしているみたいです。
ではさらに_putメソッドなどを見ていきます。

queue.py
from collections import deque


class Queue:
    # ~ 省略 ~

    # Put a new item in the queue
    def _put(self, item):
        self.queue.append(item)

    # Initialize the queue representation
    def _init(self, maxsize):
        self.queue = deque()

_putメソッドより、値はself.queueに保存されていることが分かりました。
さらに、初期化の際に用いられている_initメソッドより、self.queueはdequeインスタンスと分かります。

つまり、Queueクラスでは値の管理にcollectionsのdequeクラスを用いているようです。
ここに関しては省略しますが、dequeクラスは配列と似たように扱えそうです(多分)。

これらのことを踏まえて、Queueクラスを継承して自分好みにカスタマイズします。

queue_test.py
import queue


class MyQueue(queue.Queue):
    def show_value(self, i):
        print(self.queue[i])

    def sum(self):
        return sum(self.queue)

    def ave(self):
        return self.sum() / self.qsize()


# 動作確認
q = MyQueue()
q.put(1)
q.put(2)
q.put(6)
q.put(8)
q.show_value(2)  # 6
print(q.sum())  # 17
print(q.ave())  # 4.25

これで新しくQueueクラスを継承したMyQueueクラスを作成し、機能拡張が行えました。
無駄な操作がないためコストが小さく、なによりもコードがすっきりしました。
この程度ならまだしも、もっと複雑なことをしようとしたら大きく差が出そうですね。

まとめ

今回は既存のライブラリのqueue.Queueを継承して自分好みにカスタマイズしました。

現状できることでごり押しして解決する力も大切です。
しかし、時間・解決策があるならよりシンプルな方法を選ぶのがスマートですね。
コードがきれいになりますし、今後のためにもなります。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1