Interpreterパターン (2) ファイル検索用言語

Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)の例をPythonに変換して書いています。目次


Interpreterパターンで簡単なファイル検索言語を作ります。
パーツとしては、


All

全てのファイルを返す

FileName

パターンにマッチするファイルを返す

Bigger

指定サイズより、大きなファイルを返す

Or

2つの式のどちらかに含まれるファイルを返す

And

2つの式の両方に含まれるファイルを返す

Not

式に含まれないファイルを返す


等を用意します。

import os
import fnmatch

class Expression(object):
    pass

def allfiles(dirpath):
    """dirpathの下に含まれる、全てのファイルのリストを返す"""
    result = [] 
    for root, dirs, files in os.walk(dirpath):
        for f in files:
            result.append(os.path.join(root, f))
    return result
    

class All(Expression):
    def evaluate(self, dirpath):
        return allfiles(dirpath)


class FileName(Expression):
    def __init__(self, pattern):
        self.pattern = pattern
    
    def evaluate(self, dirpath):
        return fnmatch.filter(allfiles(dirpath), self.pattern)
    
    
class Bigger(Expression):
    def __init__(self, size):
        self.size = size
    
    def evaluate(self, dirpath):
        return [path for path in allfiles(dirpath) 
                    if os.path.getsize(path) > self.size]


class Not(Expression):
    def __init__(self, expression):
        self.expression = expression
    
    def evaluate(self, dirpath):
        result1 = set(allfiles(dirpath))
        result2 = set(self.expression.evaluate(dirpath))
        
        return list(result1 - result2)


class Or(Expression):
    def __init__(self, expression1, expression2):
        self.expression1 = expression1
        self.expression2 = expression2
    
    def evaluate(self, dirpath):
        result1 = set(self.expression1.evaluate(dirpath))
        result2 = set(self.expression2.evaluate(dirpath))
         
        return list(result1 | result2)


class And(Expression):
    def __init__(self, expression1, expression2):
        self.expression1 = expression1
        self.expression2 = expression2
    
    def evaluate(self, dirpath):
        result1 = set(self.expression1.evaluate(dirpath))
        result2 = set(self.expression2.evaluate(dirpath))
         
        return list(result1 & result2)

if __name__ == "__main__":
    from pprint import pprint
    
    expr = Not(And(Bigger(5 * 1024), FileName("*.py")))
    
    path = r"./"
    for p in expr.evaluate(path):
        print p

最後に使用例がありますが、このプログラムを実行すると、
「サイズ5kB以上の*.py ファイル」以外のファイルのリストを返します。

もちろん、その気になれば、もっと複雑な条件も作っていけます。

ちなみに、演算子オーバーロードを使えば、複雑な条件を書きやすくなります。

組み込みのset型に倣って、ビット演算子の" | "を Or、" & "を And、" ~ " を Notに使います。

class Expression(object):
    def __and__(self, other):
        return And(self, other)
    
    def __or__(self, other):
        return Or(self, other)
    
    def __invert__(self):
        return Not(self)

これで、Interpreterパターンはこれでお仕舞です。

ひとたびASTが手に入れば、式の計算なり、ファイル検索なりを行う事ができます。

問題は、どうやってASTを手に入れるかです・・・