読者です 読者をやめる 読者になる 読者になる

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

python デザインパターン

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を手に入れるかです・・・

広告を非表示にする