home changes contents help options

130:パス名がパターンにマッチするか調べる

fnmatch モジュール

パス名がパターンにマッチするかどうか調べるには fnmatch モジュールの fnmatch 関数を用います。

   >>> import fnmatch
   >>> fnmatch.fnmatch('foo.py', '*.py')
   True
   >>> fnmatch.fnmatch('foo.py', 'foo.*')
   True
   >>> fnmatch.fnmatch('foo.py', '*foo*.py')
   True
   >>> fnmatch.fnmatch('bar.py', 'ba[rz].py')
   True
   >>> fnmatch.fnmatch('bat.py', 'ba[a-z].py')
   True
   >>> fnmatch.fnmatch('ba-.py', 'ba[a-z].py')
   False
   >>> fnmatch.fnmatch('ba-.py', 'ba[-r-z].py')
   True
   >>> fnmatch.fnmatch('baz.py', 'ba?.py')
   True
   >>> fnmatch.fnmatch('ball.py', 'ba?.py')
   False
   >>> fnmatch.fnmatch('foo-bar.py', 'foo?bar.py')
   True
   >>> fnmatch.fnmatch('foo/bar.py', 'foo?bar.py')
   True
   >>> fnmatch.fnmatch('.foo.py', '?foo.py') # Ruby と異なる
   True
   >>> fnmatch.fnmatch('.foo.py', '*foo.py') # Ruby と異なる
   True

パターンは先頭のピリオドにマッチします。 Ruby のデフォルトと異なるので注意してください。 なお Ruby の File.fnmatch 第3引数における flags に相当するものはありませんのでこの動作などを変更することはできません。

fnmatch 関数は os.path.normcase 関数を用いてパス名を変換しています。 これにより大文字小文字を区別しない OS ではすべて小文字に直してからのマッチとなっています。 もし、 OS 問わず、大文字小文字を区別したければ fnmatchcase 関数を用いてください。

複数のパス名を一度にマッチするためには filter 関数を使うと便利です。

   >>> libraries = ['a.rb', 'b.so', 'a.py', 'b.pyd']
   >>> fnmatch.filter(libraries, 'a.*')
   ['a.rb', 'a.py']
   >>> fnmatch.filter(libraries, '*.pyd')
   ['b.pyd']

fnmatch の裏側は?

ドキュメントには記載されていませんが fnmatch モジュールには translate 関数があります。 パターンを与えて呼び出すと正規表現の文字列表記を返します。これを用いてマッチを行っているというわけです。

    >>> fnmatch.translate('*')
    '.*$'
    >>> fnmatch.translate('?')
    '.$'
    >>> fnmatch.translate('[a-z]')
    '[a-z]$'
    >>> fnmatch.translate('[!a-z]')
    '[^a-z]$'

fnmatch は標準ライブラリの中でもかなり短いコードのひとつであり、その行数は 107 行です。 インポートしているライブラリも re のみとなっています。読んでみるのもよいかもしれません。

    >>> import fnmatch
    >>> import os
    >>> modules = [(n, os.stat(os.path.join(ur'C:\Python25\Lib', n)).st_size) for n
    in fnmatch.filter(os.listdir(ur'C:\Python25\Lib'), '*.py')]
    >>> modules.sort(key=lambda x: x[1])
    >>> for index, (name, size) in enumerate(modules):
    ...   print "%3d: %18s %6s" % (index, name, size)
    ...
      0:  __phello__.foo.py     65
      1:             md5.py    292
      2:             sha.py    317
      3:             sre.py    397
      4:          dbhash.py    420
      5:             new.py    558
      6:         statvfs.py    794
      7:             tty.py    915
      8:            this.py   1030
      9:        dircache.py   1044
     10:        sunaudio.py   1280
     11:            user.py   1556
     12:          atexit.py   1693
     13:            stat.py   1753
     14:           mutex.py   1801
     15:            glob.py   2084
     16:         keyword.py   2138
     17:          symbol.py   2144
     18:       functools.py   2206
     19:      nturl2path.py   2302
     20:        commands.py   2363
     21:          bisect.py   2478
     22:           types.py   2643
     23:          anydbm.py   2703
     24: dummy_threading.py   2983
     25:           token.py   3067
     26:          struct.py   3069
     27:         fnmatch.py   3126
    …(中略)
    186:         mailbox.py  76716
    187:         difflib.py  82803
    188:           pydoc.py  92312
    189:         doctest.py 101589
    190:         decimal.py 188191