Pythonクックブック(3章)

dateutilモジュールの続き

rrule

繰り返し処理を簡単に出来る。

>>> from dateutil.rrule import *
>>> from datetime import *
>>> import calendar
>>> today=datetime.today()
>>> firstday=today.replace(day=1)
>>> lastday = today.replace(day=calendar.monthrange(today.year, today.month)[1])
>>> 
# 今月の火曜日、木曜日をリストアップ
>>> list(rrule(DAILY, byweekday=(TU, TH), dtstart=firstday, until=lastday))
[datetime.datetime(2011, 7, 5, 22, 17, 2), datetime.datetime(2011, 7, 7, 22, 17, 2), datetime.datetime(2011, 7, 12, 22, 17, 2), datetime.datetime(2011, 7, 14, 22, 17, 2), datetime.datetime(2011, 7, 19, 22, 17, 2), datetime.datetime(2011, 7, 21, 22, 17, 2), datetime.datetime(2011, 7, 26, 22, 17, 2), datetime.datetime(2011, 7, 28, 22, 17, 2)]
>>> 
# 今月の偶数週の水曜日をリストアップ
>>> list(rrule(WEEKLY, interval=2, byweekday=(WE), dtstart=firstday, until=lastday))
[datetime.datetime(2011, 7, 13, 22, 17, 2), datetime.datetime(2011, 7, 27, 22, 17, 2)]
>>> list(rrule(DAILY, interval=2, byweekday=(WE), dtstart=firstday, until=lastday))
[datetime.datetime(2011, 7, 13, 22, 17, 2), datetime.datetime(2011, 7, 27, 22, 17, 2)]
>>> 
# 今年の残りの月の第一水曜日をリストアップ
>>> list(rrule(MONTHLY, byweekday=WE(1), dtstart=firstday, until=datetime(2011, 12, 31)))
[datetime.datetime(2011, 7, 6, 22, 17, 2), datetime.datetime(2011, 8, 3, 22, 17, 2), datetime.datetime(2011, 9, 7, 22, 17, 2), datetime.datetime(2011, 10, 5, 22, 17, 2), datetime.datetime(2011, 11, 2, 22, 17, 2), datetime.datetime(2011, 12, 7, 22, 17, 2)]
>>> 

DAILYでもWEEKLYでも同じような結果になる。どっちを使うべきなんだろうか。。。

出勤日数の計算

[kobakoba0723@fedora13-intel64 ~]$ cat count_dayoff.py 
from dateutil import rrule
import datetime


def workdays(start, end, holidays=0, days_off=None):
    if days_off == None:
        days_off = (5, 6)
    workdays = [x for x in range(7) if x not in days_off]
    days = rrule.rrule(rrule.DAILY, dtstart=start, until=end,
                       byweekday=workdays)
    return days.count() - holidays


if __name__ == '__main__':
    testdates = [(datetime.date(2011, 7, 1), datetime.date(2011, 8, 31), 3),
                 (datetime.date(2011, 9, 1), datetime.date(2011, 11, 30), 3)
                ]

    def test(testdates, days_off=None):
        for s, e, h in testdates:
            print '\ttotal workdays from %s to %s are %s with %s holidays' % (
                s, e, workdays(s, e, h, days_off), h)

    print 'Saturday & Sunday are day off.'
    test(testdates)
    print 'Sunday is day off.'
    test(testdates, days_off=[6])
[kobakoba0723@fedora13-intel64 ~]$ python count_dayoff.py 
Saturday & Sunday are day off.
	total workdays from 2011-07-01 to 2011-08-31 are 41 with 3 holidays
	total workdays from 2011-09-01 to 2011-11-30 are 62 with 3 holidays
Sunday is day off.
	total workdays from 2011-07-01 to 2011-08-31 are 50 with 3 holidays
	total workdays from 2011-09-01 to 2011-11-30 are 75 with 3 holidays
[kobakoba0723@fedora13-intel64 ~]$ 

参考サイト

python dateutil@Labix

Python クックブック 第2版

Python クックブック 第2版

Pythonクックブック(3章)

dateutilモジュール。

インストール

サードパーティのモジュールなのでまずはインストールから。
サイトからtarballを持ってくるやり方もあるみたいだけど、easy_installでインストール可能なので、

[kobakoba0723@fedora13-intel64 ~]$ easy_install python-dateutil
Searching for python-dateutil
Reading http://pypi.python.org/simple/python-dateutil/
Reading http://labix.org/python-dateutil
Best match: python-dateutil 2.0
Downloading http://labix.org/download/python-dateutil/python-dateutil-2.0.tar.gz
Processing python-dateutil-2.0.tar.gz
Running python-dateutil-2.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-sXQGAy/python-dateutil-2.0/egg-dist-tmp-rPlpIE
Adding python-dateutil 2.0 to easy-install.pth file

Installed /home/kobakoba0723/lib/python2.6/site-packages/python_dateutil-2.0-py2.6.egg
Processing dependencies for python-dateutil
Finished processing dependencies for python-dateutil
[kobakoba0723@fedora13-intel64 ~]$ 

あれ、2.0が入っちゃった。。。
公開サイトを見ると、2.x系は1.5みたいなんだけどなぁ。まぁ、いいか。

relativedelta, rruleモジュール

dateutil.relativedelta は datetime.timedelta のような差分を扱うモジュール

>>> import dateutil
>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2011, 7, 4, 22, 26, 22, 723727)
>>> now + dateutil.relativedelta.relativedelta(months=+1)
datetime.datetime(2011, 8, 4, 22, 26, 22, 723727)
>>> now + dateutil.relativedelta.relativedelta(months=+1, weeks=+1)
datetime.datetime(2011, 8, 11, 22, 26, 22, 723727)
>>> 
>>> today = datetime.date.today()
>>> today
datetime.date(2011, 7, 4)
>>> today + dateutil.relativedelta.relativedelta(months=+1, weeks=+1)
datetime.date(2011, 8, 11)
>>> today + dateutil.relativedelta.relativedelta(months=+2, weeks=+1, hour=10)
datetime.datetime(2011, 9, 11, 10, 0)
>>> 

relativedeltaを使うと、うるう年をモジュール側でちゃんと考慮して計算してくれる。

>>> datetime.date(2008, 1, 31) + dateutil.relativedelta.relativedelta(months=+1) 
datetime.date(2008, 2, 29)
>>> datetime.date(2011, 1, 31) + dateutil.relativedelta.relativedelta(months=+1)
datetime.date(2011, 2, 28)
>>> 
>>> datetime.date(2000, 2, 29)
datetime.date(2000, 2, 29)
>>> datetime.date(2000, 2, 29) + dateutil.relativedelta.relativedelta(years=+1)
datetime.date(2001, 2, 28)
>>> datetime.date(2000, 2, 29) + dateutil.relativedelta.relativedelta(years=-1)
datetime.date(1999, 2, 28)
>>> 
>>> datetime.date(2000, 1, 1) + dateutil.relativedelta.relativedelta(nlyearday=60)
datetime.date(2000, 3, 1)
>>> datetime.date(2000, 1, 1) + dateutil.relativedelta.relativedelta(yearday=60)
datetime.date(2000, 2, 29)
>>> 

つぎにrruleモジュールと思ってhelpでも眺めようと思ったらエラーが。。。

>>> help(dateutil.rrule)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'rrule'
>>> from dateutil import rrule
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/kobakoba0723/lib/python2.6/site-packages/python_dateutil-2.0-py2.6.egg/dateutil/rrule.py", line 13, in <module>
    import _thread
ImportError: No module named _thread
>>> 

どうも_threadっていうモジュールがないからインポート処理が進まない。
調べてみると_threadは python 3 のモジュールだから進まない。python 2.x では thread って名前だった。
やっぱり、python-dateutil-2.0だったからうまくいかないのか。

アンインストール&&再インストール

easy_installで入れたんだから、easy_installで削除出来るといいなと思ってググってると、
"-mxN"オプション + モジュール削除でアンインストールできる。

[kobakoba0723@fedora13-intel64 site-packages]$ easy_install -mxN python_dateutilSearching for python-dateutil
Best match: python-dateutil 2.0
Processing python_dateutil-2.0-py2.6.egg

Using /home/kobakoba0723/lib/python2.6/site-packages/python_dateutil-2.0-py2.6.egg

Because this distribution was installed --multi-version, before you can
import modules from this package in an application, you will need to
'import pkg_resources' and then use a 'require()' call similar to one of
these examples, in order to select the desired version:

    pkg_resources.require("python-dateutil")  # latest installed version
    pkg_resources.require("python-dateutil==2.0")  # this exact version
    pkg_resources.require("python-dateutil>=2.0")  # this version or higher
[kobakoba0723@fedora13-intel64 site-packages]$ rm -rf python_dateutil-2.0-py2.6.egg/
[kobakoba0723@fedora13-intel64 site-packages]$ python
Python 2.6.4 (r264:75706, Apr  1 2010, 02:55:51) 
[GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dateutil
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named dateutil
>>> 

ちゃんとアンインストールできたようなので、次は1.5をインストールする。
easy_installで版数を指定する場合、"パッケージ名 == 版数"とやれば版数指定インストールが出来る。

[kobakoba0723@fedora13-intel64 python-dateutil-1.5]$ easy_install "python-dateutil == 1.5"
Searching for python-dateutil==1.5
Reading http://pypi.python.org/simple/python-dateutil/
Reading http://labix.org/python-dateutil
Best match: python-dateutil 1.5
Downloading http://labix.org/download/python-dateutil/python-dateutil-1.5.tar.gz
Processing python-dateutil-1.5.tar.gz
Running python-dateutil-1.5/setup.py -q bdist_egg --dist-dir /tmp/easy_install-WjExNi/python-dateutil-1.5/egg-dist-tmp-DH9yWO
Adding python-dateutil 1.5 to easy-install.pth file

Installed /home/kobakoba0723/lib/python2.6/site-packages/python_dateutil-1.5-py2.6.egg
Processing dependencies for python-dateutil==1.5
Finished processing dependencies for python-dateutil==1.5
[kobakoba0723@fedora13-intel64 python-dateutil-1.5]$

さて、今度はrruleのヘルプが見れるといいな。

>>> import dateutil
>>> help(dateutil.rrule)
>>> dateutil.rrule.rrule(dateutil.rrule.MONTHLY, dtstart=datetime.datetime(2011, 07, 04))
<dateutil.rrule.rrule instance at 0x1896e18>
>>> 

よし、見れたし、なんかうまく動きそう。続きは明日やろう。

Pythonクックブック(3章)

日付の続き、今日はdatetimeモジュール*1。明日はdateutilモジュールを使ってみる。

datetimeモジュール

基本的な日付型および時間型を扱うためのモジュール。

次の5つのクラスが属している

datetime
 |- class timedelta     : time/date/datetimeクラスの2つのインスタンスの差分を表す
 |- class tzinfo        : タイムゾーン情報の抽象基底クラス(必ず派生クラスを作成して使う)
 |- class time          : ローカルの時間(時,分,秒,マイクロ秒,タイムゾーン)を表す
 |- class date          : 日付(年,月,日)を表す
      |- class datetime : dateクラスとtimeクラスの全情報を表す
昨日、今日、明日
>>> import datetime
>>> today = datetime.date.today()
>>> yesterday = today - datetime.timedelta(days=1)
>>> tomorrow = today + datetime.timedelta(days=1)
>>> yesterday, today, tomorrow
(datetime.date(2011, 6, 27), datetime.date(2011, 6, 28), datetime.date(2011, 6, 29))
>>> print yesterday, today, tomorrow
2011-06-27 2011-06-28 2011-06-29
>>> 
1日前、現時点、1日後
>>> now = datetime.datetime.now()
>>> one_day_before = now - datetime.timedelta(days=1)
>>> one_day_after = now + datetime.timedelta(days=1)
>>> one_day_before, now, one_day_after
(datetime.datetime(2011, 6, 27, 0, 20, 17, 854036), datetime.datetime(2011, 6, 28, 0, 20, 17, 854036), datetime.datetime(2011, 6, 29, 0, 20, 17, 854036))
>>> print one_day_before, now, one_day_after
2011-06-27 00:20:17.854036 2011-06-28 00:20:17.854036 2011-06-29 00:20:17.854036
>>> 
月始め、今日、月末

月毎の最終日は、calendar.monthrange(year, month) -> [月始めの曜日、月末の日にち] から。

>>> import calendar
>>> import datetime
>>> today = datetime.date.today()
>>> calendar.monthrange(today.year, today.month)
(2, 30)
>>> first_day = today.replace(day=1)
>>> last_day = today.replace(day=calendar.monthrange(today.year, today.month)[1])
>>> first_day, today, last_day
(datetime.date(2011, 6, 1), datetime.date(2011, 6, 28), datetime.date(2011, 6, 30))
>>> print first_day, today, last_day
2011-06-01 2011-06-28 2011-06-30
>>> 

その他

組込み関数sumの第一引数には、ジェネレータを指定することが出来る。
大量のデータを扱う場合は、メモリの節約になる(はず)

[kobakoba0723@fedora13-intel64 ~]$ cat sumgenerator.py
print sum((x * x for x in range(10)))
[kobakoba0723@fedora13-intel64 ~]$ python -m pdb sumgenerator.py 
> /home/kobakoba0723/sumgenerator.py(1)<module>()
-> print sum((x*x for x in range(10)))
(Pdb) s
--Call--
> /home/kobakoba0723/sumgenerator.py(1)<genexpr>()
-> print sum((x*x for x in range(10)))
(Pdb) 
> /home/kobakoba0723/sumgenerator.py(1)<genexpr>()
-> print sum((x*x for x in range(10)))
(Pdb) 
--Return--
> /home/kobakoba0723/sumgenerator.py(1)<genexpr>()->0
-> print sum((x*x for x in range(10)))
(Pdb) 
--Call--
> /home/kobakoba0723/sumgenerator.py(1)<genexpr>()->0
-> print sum((x*x for x in range(10)))
(Pdb) 
> /home/kobakoba0723/sumgenerator.py(1)<genexpr>()->0
-> print sum((x*x for x in range(10)))
(Pdb) 

参考サイト

モジュールインデックス@Python 2.6.2 document
標準ライブラリ@Python 2.6.2 document
Pythonでの日付関連処理@Groove Labo

Python クックブック 第2版

Python クックブック 第2版

*1:ところどころでcalendarモジュールも使ってる

Pythonクックブック(3章)

昨日のめざせ会社の星を観てたら、「25分作業して5分休憩する」ってことをやってる人が出てた。
調べてみると、この方法「ポモドーロ・テクニック」って呼ばれてる時間管理術。
「○ポモドーロ」って見かけたり、翻訳本「アジャイルな時間管理術 ポモドーロテクニック入門」が出たりしてた。
「ポモドーロって何?」って気になったけど、その時は調べてなかったなぁ。
興味をもったらその時に調べる癖をつけんと、数日も経つと忘れてしまって、いつまで経っても解らんままだ。

timeモジュール

時刻データにアクセスするためのモジュール。
時刻の考え方は、UTCという世界標準時があり、そこからの時差を加味したローカル時間(日本ならJST)で表現される。

コンピュータでの時刻の管理方法は、時刻データをエポックと呼ばれる任意の時刻からの経過秒数で行っている。
エポックの値はプラットフォームに依存するが、通常1970年1月1日午前0時となっている
Pythonプログラムからは、time.gmtime(0)の値を見ればエポックがいつなのかがわかる。

>>> import time
>>> time.gmtime(0)
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)
>>> time.gmtime(0).tm_year
1970

time.gmtime()は以下の整数値からなるstruct_time(シーケンス)を返してくれる。

Index Attribute Value
0 tm_year 年[4桁]
1 tm_mon 月[1〜12]
2 tm_mday 日[1〜31]
3 tm_hour 時[0〜23]
4 tm_min 分[0〜59]
5 tm_sec 秒[0〜61]*1
6 tm_wday 曜日[0〜6]*2
7 tm_yday 1年の何日目か[0〜366]
8 tm_isdst サマータイムが有効(1)か否(0)か*3

コンピュータはepochからの経過秒数で時刻データを扱うが、
Pythonプログラムでの時刻データの扱い方は2種類ある。

  1. epochからの経過秒数として扱う
  2. struct_timeシーケンスとして扱う

Pythonプログラムで時刻を表示方法は以下の2種類ある。

  1. プログラムの内部データをそのまま表示する
  2. 人間に解りやすく加工して表示する
内部データをそのまま表示する。

出力をUTC/ローカル時間どちらで表示するのか、入出力の扱いをどうするのかで使う関数が異なる。

method 表示時刻 input output in/out反転
time.gmtime UTC epochからの秒数 struct_timeシーケンス calendar.timegm
time.localtime ローカル時間 epochからの秒数 struct_timeシーケンス time.mktime
time.strptime 文字列(,文字列形式) struct_timeシーケンス ???
人間に解りやすくして表示する。

出力の形式が固定*4/自由どちらで表示するのか、入力をどうするのかで使う関数が異なる。

method input 出力形式 表示時刻
time.asctime struct_timeシーケンス 固定 入力と同じ
time.ctime epochからの秒数 固定 ローカル時間
time.strftime struct_timeシーケンス 自由 入力と同じ

エポック以前は扱えないって書いてあるけど、2000年問題の所に1969とか書いてあるし、
実はエポック以前もいけるんじゃないかと思って試してみたけど、
epochからstruct_timeへの変換は"○"、struct_timeからepochへの変換は"×"って結果になった。

>>> time.localtime(-10**10)
time.struct_time(tm_year=1653, tm_mon=2, tm_mday=10, tm_hour=15, tm_min=32, tm_sec=19, tm_wday=0, tm_yday=41, tm_isdst=0)
>>> time.mktime(time.localtime(-10**10))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: year out of range
>>> 

色々試してみたら、tm_year=1900以降ならば動きはするらしい。まぁ、正しく動く保証は当然ないんだろうけど。

>>> time.mktime((1899,0,0,0,0,0,0,0,0))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: year out of range
>>> time.mktime((1900,0,0,0,0,0,0,0,0))
-2211786000.0
>>> 

参考サイト

モジュールインデックス@Python 2.6.2 document

Python クックブック 第2版

Python クックブック 第2版

*1:うるう秒や2重うるう秒のため61が存在

*2:0が月曜日

*3:不明な時は-1

*4:'Sun Jun 20 23:21:05 1993'

Pythonクックブック(2章)

ファイル操作の続き

特定ディレクトリの探索(glob)

globモジュールは、Unix 形式のパス名のパターン展開をしてくれるので、
下位ディレクトリを調べる必要がないような探索にはos.walk/fnmatchを使わなくてもよい。
globモジュール自体が、os.listdir()/fnmatch.fnmatch()を使っているからなのか。

[kobakoba0723@fedora13-intel64 ~]$ cat findgrepglob.py 
import os
import glob


def find_grep(search_dir, pattern='*'):
    for match in glob.glob(os.path.join(search_dir, pattern)):
        yield match
    # for search_dir in search_dirs.split(os.pathsep):
    #     for pattern in patterns.split(';'):
    #         for match in glob.glob(os.path.join(search_dir, pattern)):
    #             yield match


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print 'Usage: python %s <search_dirs> <patterns>' % (sys.argv[0], )
    for search_dir in sys.argv[1].split(os.pathsep):
        for pattern in sys.argv[2].split(';'):
            for file in find_grep(search_dir, pattern):
                print file
[kobakoba0723@fedora13-intel64 ~]$ python findgrepglob.py "$(echo $PATH)" '*.sh'
/usr/bin/gnome-power-bugreport.sh
/usr/bin/pm-utils-bugreport-info.sh
/usr/bin/amuFormat.sh
/usr/bin/pv.sh
/usr/bin/lprsetup.sh
/usr/bin/lesspipe.sh
/usr/bin/setup-nsssysinit.sh
/usr/bin/packagekit-bugreport.sh
/usr/bin/unix-lpr.sh
[kobakoba0723@fedora13-intel64 ~]$ 

findの検索対象に複数ディレクトリ指定できるってのがわかって嬉しかった。
nameオプションに複数のパターンを渡すことって出来ないんだろうか。

[kobakoba0723@fedora13-intel64 ~]$ find $(echo $PATH | sed 's/:/ /g') -type f -name "*.sh"
/usr/bin/gnome-power-bugreport.sh
/usr/bin/pm-utils-bugreport-info.sh
/usr/bin/amuFormat.sh
/usr/bin/pv.sh
/usr/bin/lprsetup.sh
/usr/bin/lesspipe.sh
/usr/bin/setup-nsssysinit.sh
/usr/bin/packagekit-bugreport.sh
/usr/bin/unix-lpr.sh
[kobakoba0723@fedora13-intel64 ~]$ 

2日前、Usageメッセージのモジュール名ハードコーディングしてるし。。。恥ずかし。
それと、which 相当のことをやる時に、$PATHの内容をソースにハードコーディングしてたけど、
osモジュールのenvironっていうディクショナリに'PATH'でアクセス(os.environ['PATH'])すれば良かったんだ。

その他(itertools)

itertoolsはイテレータを生成するモジュール。
itertools.zip()はイテレータブルオブジェクトを受け取って、リストのイテレータを返す。

>>> import itertools
>>> for elements in itertools.izip(['a', 'b', 'c', 'd'], ['a', 'b', 'e', 'd']):
...     print elements
... 
('a', 'a')
('b', 'b')
('c', 'e')
('d', 'd')
>>> for elements in itertools.izip_longest('spam', 'egg', 'snake'):
...     print elements
... 
('s', 'e', 's')
('p', 'g', 'n')
('a', 'g', 'a')
('m', None, 'k')
(None, None, 'e')
>>> 

その他(python以外)

昨日は、初めてソースにパッチを当てたり、srpmからrpmを作ったりしてみた。
LPIの試験でコマンドの使い方は知ってるつもりだったけど、いざやってみると出来ないもんだ。
実機上で始めて動いたら何ともいえず嬉しかった、しょうもないことしかやってないけど。

参考サイト

モジュールインデックス@Python 2.6.2 document
RPMバージョン4.8の機能と変更点@HDE コラム
ニュースレター20号@レッドハット

Python クックブック 第2版

Python クックブック 第2版

Pythonクックブック(2章)

ファイル操作の続き

ディレクトリの探索(os.walk, fnmatch)

os.walk(top)を実行すると、(dirpath, subdirnames, filenames) を返す。

[kobakoba0723@fedora13-intel64 ~]$ ls -R top/
top/:
eggs.txt  spam.txt  sub

top/sub:
hoge.data  piyo.data

>>> import os
>>> for (dirpath, subdirs, files) in os.walk('top'):
...     print dirpath
...     print files, subdirs
... 
top
['spam.txt', 'eggs.txt'] ['sub']
top/sub
['piyo.data', 'hoge.data'] []
>>> 

fnmatchは シェルのワイルドカードを使ったファイル名のマッチングをするモジュール。
fnmatch.fnmatch(name, pattern)はファイル名nameが パターンpattern にマッチした場合 True を返す

この2つを組み合わせると find | grep と同じようなことが出来る。

[kobakoba0723@fedora13-intel64 ~]$ find top/ | grep -e '.*\.txt' -e '.*\.data'
top/spam.txt
top/eggs.txt
top/sub/piyo.data
top/sub/hoge.data
[kobakoba0723@fedora13-intel64 ~]$ 

fnmatchはシェルのワイルドカードを解釈し、grep正規表現を解釈するので実行時の引数が違う。

[kobakoba0723@fedora13-intel64 ~]$ cat findgrep.py 
import os
import fnmatch


def find_grep(root, patterns='*'):
    patterns = patterns.split(';')
    for (dirpath, subdirs, files) in os.walk(root):
        files.sort()
        for name in files:
            for pattern in patterns:
                if fnmatch.fnmatch(name, pattern):
                    yield os.path.join(dirpath, name)
                    break


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print 'Usage: python findgrep.py <top> <pattern>'
        sys.exit(1)
    for filename in find_grep(sys.argv[1], sys.argv[2]):
        print filename
[kobakoba0723@fedora13-intel64 ~]$ python findgrep.py 'top' '*.txt;*.data'
top/eggs.txt
top/spam.txt
top/sub/hoge.data
top/sub/piyo.data
[kobakoba0723@fedora13-intel64 ~]$ 

拡張子の変更(os.walk, os.rename)

os.rename(src, dst) は mv src dst と同じ。
os.walk と os.rename を組み合わせると、シェルの forループ + mv と同じことが出来る。

[kobakoba0723@fedora13-intel64 ~]$ ls -R top.shell/
top.shell/:
eggs.txt  spam.txt  sub

top.shell/sub:
hoge.data  piyo.data
[kobakoba0723@fedora13-intel64 ~]$ for file in $(find top.shell/ -type f | grep .*\.txt)
> do
>   mv $file ${file%%.txt}.data
> done
[kobakoba0723@fedora13-intel64 ~]$ ls -R top.shell/
top.shell/:
eggs.data  spam.data  sub

top.shell/sub:
hoge.data  piyo.data
[kobakoba0723@fedora13-intel64 ~]$
[kobakoba0723@fedora13-intel64 ~]$ cat swapextension.py 
import os


def swap_extension(dir, before, after):
    if before[:1] != '.':
        before = '.' + before
    extension_len = -len(before)
    if after[:1] != '.':
        after = '.' + after
    for (dirpath, subdirs, files) in os.walk(dir):
        for filename in files:
            old_filepath = os.path.join(dirpath, filename)
            new_filepath = old_filepath[:extension_len] + after
            os.rename(old_filepath, new_filepath)


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 4:
        print "Usage: python swapextension.py <rootdir> <before> <after>"
        sys.exit(1)
    swap_extension(sys.argv[1], sys.argv[2], sys.argv[3])
[kobakoba0723@fedora13-intel64 ~]$ ls -R top.py/
top.py/:
eggs.txt  spam.txt  sub

top.py/sub:
hoge.data  piyo.data
[kobakoba0723@fedora13-intel64 ~]$ python swapextension.py top.py '.txt' '.data' 
[kobakoba0723@fedora13-intel64 ~]$ ls -R top.py/
top.py/:
eggs.data  spam.data  sub

top.py/sub:
hoge..data  piyo..data
[kobakoba0723@fedora13-intel64 ~]$ 

サーチパスからファイルを探す(os.path)

os.path モジュールはプラットフォームに依存しない共通のパス名操作モジュール
このモジュールを使うと、which コマンドと同じようなことが出来る。

[kobakoba0723@fedora13-intel64 ~]$ which pep8
~/bin/pep8
[kobakoba0723@fedora13-intel64 ~]$ which pep
/usr/bin/which: no pep in (/home/kobakoba0723/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin)
[kobakoba0723@fedora13-intel64 ~]$ 

今の状態はenv_pathを固定でやってしまっているので、
シェル変数$PATHから値を持って来れるようになれば良いんだけどなぁ。

[kobakoba0723@fedora13-intel64 ~]$ cat searchcmd.py 
import os


def search_command(command, search_path, path_sep=os.pathsep):
    for path in search_path.split(os.pathsep):
        candidate = os.path.join(path, command)
        if os.path.isfile(candidate):
            return os.path.abspath(candidate)


if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print 'Usage: python searchcmd.py <command>'
        sys.exit(1)
    search_cmd = sys.argv[1]
    env_path = ['/home/kobakoba0723/bin', '/usr/local/bin',
                '/usr/bin', '/usr/local/sbin', '/usr/sbin', '/sbin']
    search_path = os.pathsep.join(env_path)
    cmd_path = search_command(search_cmd, search_path)
    if cmd_path:
        print "Command '%s' found in '%s'" % (search_cmd, cmd_path)
    else:
        print "Command '%s' not found in '%s'" % (search_cmd, search_path)
[kobakoba0723@fedora13-intel64 ~]$ python searchcmd.py pep8
Command 'pep8' found in '/home/kobakoba0723/bin/pep8'
[kobakoba0723@fedora13-intel64 ~]$ python searchcmd.py pep
Command 'pep' not found in '/home/kobakoba0723/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin'
[kobakoba0723@fedora13-intel64 ~]$ 

参考サイト

モジュールインデックス@Python 2.6.2 document

Python クックブック 第2版

Python クックブック 第2版

Pythonクックブック(2章)

ファイル操作の続き

zipファイルを扱う(zipfile, tempfile)

zipfileモジュールを使うとzipファイルの内容物を表示したり、展開したりできる。

ZipFile.namelist()を使うと内容物の名前を取得できるけど、
ZipFile.infolist()を使うと内容物に関する名前とかサイズとか更新日なんかも取得できる。

>>> import zipfile
>>> z = zipfile.ZipFile("xml_parse.zip", "r")
>>> for info in z.infolist():
...   print 'File:', info.filename, 'has', info.file_size, 'bytes'
... 
File: xml_parse/ has 0 bytes
File: xml_parse/comps.xml has 1726498 bytes
File: xml_parse/filelists.xml has 35256400 bytes
File: xml_parse/sax_parser.py has 6532 bytes
>>> 

tempfileモジュールを使うと一時ファイルを作成することが出来る。
tempfile.mkstemp()を使うと一時ファイルを作成できるけど、使い終わったら自分で消さないといけない。
tempfile.NamedTemporaryFile()だと使い終わったら自動的に消してくれる。
tempfile.NamedTemporaryFile()とtempfile.TemporaryFile()の違いは、ファイルの名前があるのかないのか。

>>> import zipfile, tempfile, sys
>>> file = tempfile.NamedTemporaryFile(mode='w+t', suffix='.zip')
>>> file.name
'/tmp/tmpcbTHIk.zip'
>>> z = zipfile.ZipFile(file.name, 'w')
>>> z.writestr('hello.py', 'def f(): return "hello world from "+__file__\n')
>>> z.close()
>>> sys.path.insert(0, file.name)
>>> import hello
>>> print hello.f()
hello world from /tmp/tmpcbTHIk.zip/hello.py
>>> 

でもなんで/tmp/tmpcbTHIk.zip内のhello.pyを直接インポートできるのかわからない。
zipfileモジュールを一切介さずインポートしようとするとエラーが出るから、zipfileモジュールが関連してるんだろうな。

>>> import sys
>>> sys.path.insert(0, '/home/kobakoba0723/hello_world.zip')
>>> import hello
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named hello
>>> 
[kobakoba0723@fedora13-intel64 ~]$ ls
TestTool      cedet-1.0.tar.gz  lib                テンプレート  画像
TestTool.bak  exec.data         search_string      デスクトップ  公開
anaconda      ez_setup.py       virtual-python.py  ドキュメント
bin           hello_world.zip   xml_parse.zip      ビデオ
cedet-1.0     include           ダウンロード       音楽
[kobakoba0723@fedora13-intel64 ~]$ unzip hello_world.zip 
Archive:  hello_world.zip
   creating: hello_world/
  inflating: hello_world/hello.py    
[kobakoba0723@fedora13-intel64 ~]$ 

tarファイルを扱う(tarfile)

tarfileモジュールを使うことで、作成から展開まで行える。

[kobakoba0723@fedora13-intel64 ~]$ cat make_tar.py 
import tarfile
import os


def make_tar(folder_to_backup, dest_folder, compression='gz'):
    if compression:
        dest_ext = '.' + compression
    else:
        dest_ext = ''
    arcname = os.path.basename(folder_to_backup)
    dest_name = '%s.tar%s' % (arcname, dest_ext)
    dest_path = os.path.join(dest_folder, dest_name)
    if compression:
        dest_cmp = ':' + compression
    else:
        dest_cmp = ''
    out_file = tarfile.open(dest_path, 'w' + dest_cmp)
#    out_file.add(folder_to_backup)
    out_file.add(folder_to_backup, arcname)
    out_file.close()
    return dest_path


def listup_tar(filename):
    in_file = tarfile.open(filename, 'r')
    print in_file.list()


def unpack_tar(filename):
    in_file = tarfile.open(filename, 'r')
    in_file.extractall(path='./unpack')


if __name__ == '__main__':
    filename = make_tar('/home/kobakoba0723/search_string',
                        '/home/kobakoba0723/',
                        'gz'
                       )
    print filename
    listup_tar(filename)
    unpack_tar(filename)

tarfile.addの第2引数を指定しなければ、dest_pathをトップから全てzipにしてくれる。
第2匹数を指定すれば、指定したところからzipにしてくれる

[kobakoba0723@fedora13-intel64 ~]$ ls
TestTool      cedet-1.0         include        unpack             デスクトップ  画像
TestTool.bak  cedet-1.0.tar.gz  lib            virtual-python.py  ドキュメント  公開
anaconda      exec.data         make_tar.py    ダウンロード       ビデオ
bin           ez_setup.py       search_string  テンプレート       音楽
[kobakoba0723@fedora13-intel64 ~]$ ls unpack/
[kobakoba0723@fedora13-intel64 ~]$ python make_tar.py 
/home/kobakoba0723/search_string.tar.gz
-rwxrwxr-x kobakoba0723/kobakoba0723          0 2011-06-22 23:02:23 search_string/
-rw-rw-r-- kobakoba0723/kobakoba0723         56 2011-04-10 18:14:20 search_string/snmpd.options
-rw-r--r-- kobakoba0723/kobakoba0723       1549 2011-04-10 17:26:47 search_string/profile
-rw-rw-r-- kobakoba0723/kobakoba0723        547 2011-04-10 18:26:35 search_string/search_string.py
None
[kobakoba0723@fedora13-intel64 ~]$ ls unpack/
search_string
[kobakoba0723@fedora13-intel64 ~]$

参考サイト

モジュールインデックス@Python 2.6.2 document

Python クックブック 第2版

Python クックブック 第2版