Pythonの技法:リスト以外の選択肢「関数の生成」

文:Nick Gibson(Builder AU)  翻訳校正:原井彰弘
2008-01-17 15:10:00
  • このエントリーをはてなブックマークに追加

 Factoryパターンは、呼び出し時にパラメータとして渡された値に応じて、さまざまなクラスを返すクラスだ。類似の機能としては関数ファクトリがある。ただし、ここではクラスを返すのではなく、状態に応じていろいろな関数を返す関数を作成する。Pythonでは関数はファーストクラスオブジェクトであるため、これは簡単に記述することが可能だ。

def colourfactory(red, green, blue, steps):
        def cf(i):
                def f((start,end), i, steps):
                        return start + i * (end-start)/float(steps)
                rgb = tuple([f(c,i,steps-1) for c in [red,green,blue]])
                return "#%02x%02x%02x" % (rgb)
        return cf

 colourfactory関数を先ほどのcolourlist関数と同じパラメータで呼び出すと、colourlist関数と同様の結果を生成する関数を返す。ただし、この関数を同じリストを用いた先ほどの例と比較すると、関数を呼び出すコストがそれぞれの結果に対して付加されるため、残念ながらこちらの方が効率が悪くなってしまう。しかし、関数を作成した方が解決策としてずっと適切な場合もある。以下に、その状況を3つ挙げよう。

  • 疎なリストが必要な場合 -- リストすべてが必要ない場合、リストすべてを生成するのに時間をかけるのは無駄である。関数ファクトリを使用すると、必要な結果のみが取得できるようになる。
  • バラバラの順序でリストが必要な場合 -- ランダムな順序でリストの結果にアクセスしたい場合、必要な順番に生成を行った方が簡単で明瞭である。関数ファクトリのアプローチを用いると、自分の好きな順序で結果を取得できる。
  • リスト全体をメモリに保持することが不可能な場合 -- 1億の結果を生成して作業を行わなければならないが、リストのそれぞれの結果は一度しか用いない場合、すべての結果をメモリ上に保持するのは無意味である。関数ファクトリを用いると、結果を一つずつ作成することが可能になる。

 上記の最後のケースでは、リストの内包表記ではなくジェネレータを用いれば、簡単に実現することが可能である。ただし、ジェネレータを用いた場合には、あらかじめ定められた順序でアイテムを生成しなければならないという問題点がある。ジェネレータでは、ランダムアクセスを簡単に行うことはできないのだ。以下に、ジェネレータを用いて実装した先ほどの色の生成プログラムを示す。

def coloursgenerator(red,green,blue,steps):
        for i in range(steps):
                def f((start,end), i, steps):
                        return start + i * (end-start)/float(steps)     
                rgb = tuple([f(c,i,steps-1) for c in [red,green,blue]])
                yield "#%02x%02x%02x" % (rgb)

 さて、関数によるアプローチがより適切である例を考えてみよう。まず、システムの負荷の一覧があると仮定する。これは、UnixやLinuxのuptimeユーティリティが生成するようなものだと思ってもらえればよい。そして、システムの負荷の大きさを視覚的に表現するために、色を付けたいとする。我々が今望んでいるのは、次のような表示だ。

load average: 0.36 0.43 0.48
load average: 4.23 1.87 0.98
load average: 3.97 2.67 1.39
load average: 4.01 3.03 1.61

 ここでは、テーブルのセルに色づけをする際に、負荷の量を色の階調の度合いとして使用している(最大値はとりあえず5.00としておこう)。この実装は次のようになるだろう。

load_averages = 
    [[0.36, 0.43, 0.48], 
     [4.23, 1.87, 0.98], 
     [3.97, 2.67, 1.39], 
     [4.01, 3.03, 1.61]]

colourfunc = colourfactory((255,255), (255,0), (255,0), 500)

print ""
for load in load_averages:
        print ""
        for l in load:
                print "" % (colourfunc(l*100),l)
        print ""
print "
load average: %s
"

 常に最適なアプローチというものは存在しない。しかし、どのような選択肢が存在するのかを知っていれば、もっとも適切で、もっとも効率的で、かつもっとも保守が容易な書き方ができるツールを持っていると確信することができるだろう。たとえば、アルゴリズムの実行速度を優先させるために、データはできる限り前もって計算しておきたいこともある。その場合は、リストが間違いなく適した手法であるだろう。しかし、ランダムアクセスを行ったり、必要になる可能性のあるデータは多数あるものの実際には少数しか使用されない場合には、関数によるアプローチを最初に試みるべきだろう。

このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]