目次
- 1 Pythonでのファイルの取扱と文字コード
- 1 単純なファイルの読み込み
- 2 少しずつ読み取る
- 3 文字コードを指定して開く
- 4 バイナリで開く
- 5 bytesとstrの関係
- 6 特定の文字列の出現回数を数える
- 7 標準入出力をファイルのように扱う
- 2 文字列のフォーマット
- 1 formatメソッド
- 2 formatメソッドで数値を表示する
- 3 プロパティやサブスクリプションをフォーマット文字列で扱う
- 4 フォーマットの注意点
- 5 ユーザー定義クラスにフォーマットを定義する
- 3 さらにテキストファイルを極める
- 1 複数ファイルを扱うためのfileinput
- 2 文字列検索ユーティリティ
- 3 行の分割
- 4 集計
- 5 データを表示する
- 4 Pythonオブジェクトでデータ処理
- 1 TODOリスト
- 2 データ構造を決める
- 3 タスクを作成して登録する
- 4 データを操作する
- 5 タスクを保存する
- 6 TODOリストファイル
- 7 タスクをShelfに保存する
- 8 タスクをすべて取り出す
- 9 タスクをフィルタリングする
- 5 コマンドラインアプリケーションとコマンドライン引数
- 1 スクリプトファイルを実行可能にする
- 2 コマンドライン引数
- 3 コマンドラインオプション
- 4 サブコマンド
- 6 TODOアプリケーション
- 1 アプリケーションの開始点
- 2 サブコマンドごとの処理
- 7 まとめ
ファイルを開いて、読み込んで、閉じる
f = open('test.txt') ## ファイルをオープン
print( f.read(), end="") ## ファイルを読み込み
f.close()
f.read()
とするとファイルを全部読み込むことができる
ファイルライクオブジェクト(f.openの戻り値)は一行分ずつstrを返すiteratorなので、for文で一行ずつループすることができる。
f = open('test.txt') ## ファイルをオープン
for line in f:
print(line, end="")
f.close()
iteratorについてはここらへんを参考にしてください http://jutememo.blogspot.jp/2008/06/python.html
open関数の引数で ** encoding ** を指定することでその文字コードで読み込むことができる
f = open('text.txt', encoding='utf-8')
例)
IHR ihr
foo bar
xhr xhr
presto (・w・)
こんなファイルをutf-8で保存して
>>> f = open('test.txt', encoding='utf-8')
>>> print(f.read(), end="")
とすると、きちんと表示される。EUC-JPで表示しようとすると
>>> f = open('test.txt', encoding='euc-jp')
>>> print(f.read(), end="")
UnicodeDecodeError: 'euc_jp' codec can't decode byte 0x88 in position 33: illegal multibyte sequence
と出てきて読み込むことが出来ない
open関数の引数で ** mode ** にbを追加するとバイナリ読み込みモードとなる。 上のtxtファイルを読み込んで表示すると次のようになる
>>> f = open('text.txt', mode='rb')
>>> f.read()
b'IHR ihr\nfoo bar\nxhr xhr\npresto \xef\xbc\x88\xe3\x83\xbb\xef\xbd\x97\xe3\x83\xbb)\n'
strオブジェクトは内部で文字列をユニコードで扱っています。strオブジェクトをコンソールやファイルに出力するには bytesオブジェクトのバイト列に戻す必要があります。
>>> s = 'あ'
>>> print(s)
あ
>>> print(s.encode('utf-8'))
b'\xe3\x81\x82'
>>> b = b'あ' ## syntax error になる
>>> b = b'\xe3\x81\x82' ## これなら大丈夫
>>> print( b.decode('utf-8') )
あ
#!/usr/bin/env python3.3
with open('test.txt') as f:
search = input() ## wait for input
count = 0
for line in f:
if line.find(search) > -1: ## cannot use line.indexof
count += 1
print(count)
input()
は標準入力を待つメソッドっぽい。(2.7では使えなかった)
3.3だと書籍に書いてあったindexofがstrオブジェクトになかったので代わりに似たようなfindメソッドを使いました
標準モジュールであるsysモジュールのsys.stdin
やsys.stdout
を使うことでファイルの入出力のように扱うことができます。
#!/usr/bin/env python3.3
import sys
def echo(in_, out):
for line in in_ :
out.write(line) ## そのまま出力
out.write( "{0} {1}".format(len(line), line)) ## 先頭に文字数を表示
echo(sys.stdin, sys.stdout)
strオブジェクトを良い感じにフォーマットする。割と便利っぽいけど、
>>> print ("%d, %lf, %e" %(10, 20.004, 10.00))
10, 20.004000, 1.000000e+01
のように%でフォーマット指定子使うことができるし、pprintというモジュールもあるのでそっちを使うことも検討したほうがいいかもしれない。
フォーマットするにはformatメソッドを利用します。
>>> "{0:04}".format(2)
0002
{0:04}は、formatメソッドの0番目の引数で入れ替えられます。今回は2が入れ替えられます。":"以降がフォーマットで、"04"としている今回は4桁で表示して0でで埋め合わせます.
>>> s = "{0:04} -> {1:04}"
>>> s.format(2,8)
'0002 -> 0008'
","をフォーマットに指定することで、3桁ごとに","で区切った文字列が表示されます。
>>> "---{0:,}---".format(1234567890)
'---1,234,567,890---'
プレースホルダの指定は引数だけでなくそのオブジェクトのプロパティを指定出来ます。
>>> class Point:
>>> def __init__(self, x, y):
>>> self.x = x
>>> self.y = y
>>>
>>> p = Point(10,20)
>>> str_ = "({0.x:04}, {0.y:04})".format(p)
>>> print(str_)
(0010, 0020)
listやdictも利用することができます。
>>> list = ['foo', 'bar', 'ihr', 'presto']
>>> "{0[0]}, {0[3]}".format(list)
'foo, presto'
>>> dict = { 'foo' : 'bar', 'ihr' : 'IHRIHR' }
>>> "{0[foo]}, {0[ihr]}".format(dict)
'bar, IHRIHR'
辞書のキーに指定する際に、ダブルクォーテーションやシングルクォーテーションは必要ないので注意が必要
>>> dict = { 'foo' : 'bar', 'ihr' : 'IHRIHR' }
>>> dict['foo']
'bar'
>>> "{0['foo']}".format(dict)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: "'foo'"
>>> "{0[foo]}".format(dict)
'bar'
辞書のキーで数字をしていすると取り出せないこともあり得る。
__format__
メソッドをオーバーライドすることで format(obj)
のようにしてフォーマットすることができる。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __format__(self, spec):
return ("({0.x:"+spec+"}, {0.y:"+spec+"})").format(self)
p = Point(10,20)
print (format(p, "04"))
実行結果
(0010, 0020)
もう少し実用的なテキストファイルの触り方です
複数のファイルを一括で扱うためのモジュールとしてfileinputがあります。 複数のファイル名を入力として受け取り、それらのファイルの各行を順番にiteratorとして扱います。
with fileinput.FileInput(files=sys.argv[1:]) as f:
for line in f:
としたとき、lineには1つ目のファイルの1行目, 2行目...最終行, 2つ目のファイルの1行目, 2行目... という風にはりいます。
-----------test1.txt----------
aaa
bbb
ccc
ddd
eee
-----------test2.txt----------
ihr
ihr
ihr
presto
shigeta
-----------test3.txt----------
satou haruki
asami yuma
ogura yuzu
julia
yoshizawa akiho
siina yuna
tsubomi
sakura mana
としたとき、
#!/usr/bin/env python3.3
import sys, fileinput
with fileinput.FileInput(files=sys.argv[1:]) as f:
for line in f:
print(line, end="")
の出力は
aaa
bbb
ccc
ddd
eee
ihr
ihr
ihr
presto
shigeta
satou haruki
asami yuma
ogura yuzu
julia
yoshizawa akiho
siina yuna
tsubomi
sakura mana
となります。
いくつかのモジュールを使いながら文字列検索ユーティリティを作ります。
- fileinput : ファイルの読み込み
- argpase : コマンドライン引数を良い感じに扱ってくれる
#!/usr/bin/env python3.3
import sys, fileinput, argparse
parser = argparse.ArgumentParser()
parser.add_argument('-e', '--encoding', default=None)
parser.add_argument('search', help="search word")
parser.add_argument('files', nargs="+")
args = parser.parse_args()
search = args.search
def enc_open(filename, mode):
return open(filename, mode=mode, encoding = args.encoding )
with fileinput.FileInput(files=args.files, openhook=enc_open) as f:
for line in f:
if line.find(search) > -1:
print("{0:20}:{1:4} {2}".format( f.filename(), f.lineno(), line), end="")
各行を分割するにはsplitメソッドを使います。
>>> str = "hoge,fuga,piyo"
>>> str.split(',')
['hoge', 'fuga', 'piyo']
splitメソッドは引数としてセパレータ文字列と最大分割数を渡すことができます。デフォルトでは空白文字で分割します。
さらに、文字列の先頭や終端の文字を削除するにはstripメソッドを用います。こちらも削除対象の文字列を渡すことができますが、デフォルトでは空白文字です。
>>> " ihrihr ".strip() ## デフォルトでは空白文字が削除される
'ihrihr'
>>> "xxxxihrihrxxxx".strip('x') ## 削除する文字を指定する
'ihrihr'
先頭だけ削除したい場合はlstrip
を、終端だけ削除したい場合はrstrip
を用います。
csvのようなカンマ区切りのテキストファイルのデータを辞書にまとめます。 女優名をキーに、出演タイトル数を返す辞書を作ります。
def sum_product(filename):
with open(filename) as f:
results = {}
for line in f:
parts = line.split(",")
name = parts[0]
count = int(parts[1])
last_count = results.get(name, 0) ## 重複してファイルにあった場合は加算する
results[name] = count + last_count
return results
こんなデータファイルがあったときに、先ほどのsum_product関数を用います
// 女優名, 出演タイトル数, バスト, ウエスト, ヒップ
satou haruki, 456, 89, 59, 88
asami yuma, 363, 96, 58, 88
ogura yuzu, 68, 83, 59, 83
julia, 317, 101, 55, 84
yoshizawa akiho, 547, 86, 58, 86
siina yuna, 217, 88, 58, 85
tsubomi, 941, 84, 58, 85
sakura mana, 20, 89, 56, 89
実行内容
results = sum_product('test3.txt')
for key, value in results.items():
print(key, value)
実行結果
ogura yuzu 68
sakura mana 20
tsubomi 941
asami yuma 363
satou haruki 456
julia 317
yoshizawa akiho 547
siina yuna 217
TODOリストをテキストファイルで管理するアプリケーションを作ります。
使うモジュール
- datetime
- pickle
- shelve
spec:
- TODOリストはタスクの一覧を持つ
- タスクは、名前と締切日、予測時間を持つ
タスクのデータ構造は
- 名前(name) : str
- 締切日(due_date) : datetimeモジュールのdatetime (datetime.dateも使えるがパーサーなどの都合でこちらのほうが良い)
- 予測時間(required_time) : datetime.time
- 状態(finished) : Bool
これらを辞書で持つ
タスクを作成する関数create_task
を以下のように定義します
def create_task(name, due_date, required_time):
return dict( name=name, due_date=due_date, required_time=required_time, finished=False)
次のように使います。
#!/usr/bin/env python3.3
# -*- coding: utf-8 -*-
from datetime import datetime, time
name = "最初のタスク"
due_date = datetime(2013, 6, 16)
tm = time(0,20)
task = create_task(name, due_date, tm)
print(task) ## {'finished': False, 'due_date': datetime.datetime(2013, 6, 16, 0, 0), 'required_time': datetime.time(0, 20), 'name': '最初のタスク'}
フォーマットする関数を追加します
def format_task(task):
state = "完了" if task['finished'] else "未完了"
format = "{state} {task[name]} : {task[due_date]:%Y-%m-%d} まで 予定所要時間 {task[required_time]}分"
return format.format(task=task, state=state)
>>> task = create_task(name, due_date, tm)
>>> print(format_task(task))
未完了 最初のタスク : 2013-06-16 まで 予定所要時間 00:20:00分
タスクを完了する関数を追加
def finish_task(task):
task['finished'] = True
pickleモジュールを使うことでオブジェクトをファイルに保存することができます。
import pickle
def save_task(task, filename):
pickle.dump(task, open(filename, "wb"))
def load_task(filename):
return pickle.load(open(filename, "rb"))
使い方
task = create_task("最初のタスク", datetime(2013,6,16), time(0,20))
task2 = create_task( "4章を作る", datetime(2013, 6, 17), time(0, 30))
tasks = [task, task2]
save_task(tasks, "tasks.txt")
saved_tasks = load_task("tasks.txt")
print( saved_tasks )
タスクはリストに入れています。
出力結果
[{'name': '最初のタスク', 'required_time': datetime.time(0, 20), 'finished': False, 'due_date': datetime.datetime(2013, 6, 16, 0, 0)}, {'name': '4章を作る', 'required_time': datetime.time(0, 30), 'finished': False, 'due_date': datetime.datetime(2013, 6, 17, 0, 0)}]
注意点)
- pickleモジュールはバイナリモードで開く必要があります
- バイナリで保存されるのでそのままでは読めません
キーを指定してオブジェクトを格納して、ファイルに保存してくれるshelveモジュールのShelfオブジェクトを利用します
>>> import shelve
>>> db = shelve.open("data_store", "c") ## Shelfオブジェクトを生成. 保存ファイルとして data_store というファイルを利用
>>> len(db)
0
>>> db['test_data'] = {"message":"Hello, world!!"} ## test_data をキーとしてマップを登録
>>> db.close() ## data_store に書き込み
>>> db = shelve.open("data_store", "c")
>>> len(db)
1
>>> db['test_data']
{'message': 'Hello, world!!'}
shelveの保存にはpickleモジュールを利用します
先ほど用いたshelfを利用して、タスクを保存します。shelfにデータを保存するにはキーが必要となります。ここでは連番を生成して順次キーを生成する関数を作ります。連番の元になる値もshelfに保存します。
def next_task_key(db):
id_ = db.get('next_id', 0)
db['next_id'] = id_ + 1
return "task:{0}".format(id_)
こんなかんじで使います
db = shelve.open("task.db", "c")
task = create_task("最初のタスク", datetime(2013,6,16), time(0,20))
task2 = create_task( "4章を作る", datetime(2013, 6, 17), time(0, 30))
add_task(db, task)
add_task(db, task2)
さらにこの関数を用いてshelfにタスクを追加する関数を追加します。
def add_task(db, task):
key = next_task_key(db)
db[key] = task
すべてのタスクを取り出して表示するには次のようにします。
db = shelve.open("task.db", "c")
for key in db:
print (key, db[key])
出力はこんな感じ
task:1 {'finished': False, 'required_time': datetime.time(0, 30), 'name': '4章を作る', 'due_date': datetime.datetime(2013, 6, 17, 0, 0)}
task:0 {'finished': False, 'required_time': datetime.time(0, 20), 'name': '最初のタスク', 'due_date': datetime.datetime(2013, 6, 16, 0, 0)}
next_id 2
こんな感じに条件を追加して、"task:" で始まるキーをまとめて返す関数を作ります。
def all_tasks(db):
for key in db:
if key.startswith('task:'):
yield key, db[key]
yieldは必要になるまで処理を行わない、ジェネレータのための何からしいです。
こんな感じで使います。
db = shelve.open("task.db", "c")
for k, v in all_tasks(db):
print(k,v)
(k,v)のループが実行されるまでall_tasksのループは実行されません。
未完了のタスクのみを取り出します。
def unfinished_task(db):
return ((key, task) for (key, task) in all_tasks(db) if not task['finished'])
使い方と実行結果
>>> db = shelve.open("task.db", "c")
>>> for k, v in unfinished_task(db):
>>> print(k,v)
task:1 {'required_time': datetime.time(0, 30), 'due_date': datetime.datetime(2013, 6, 17, 0, 0), 'finished': False, 'name': '4章を作る'}
task:0 {'required_time': datetime.time(0, 20), 'due_date': datetime.datetime(2013, 6, 16, 0, 0), 'finished': False, 'name': '最初のタスク'}
chmod +x
してください
コマンドライン引数をうけとるにはsysモジュールをインポートしてsys.argv
の中に入っています
こんなプログラムを書いて
#!/usr/bin/env python3.3
import sys
for a in sys.argv: print(a)
実行する
$ ./sample.py a b c d
./sample.py
a
b
c
d
もう少し複雑なコマンドライン引数を良い感じに扱ってくれるargparseで取り扱う
### arg_parse.py
#!/usr/bin/env python3.3
import argparse
parser = argparse.ArgumentParser() ## インスタンス生成
parser.add_argument('-n', '--name', default="world") ## -n, --name 引数を指定
args = parser.parse_args() ## 引数をパース
print("Hello, {name}!!".format(name=args.name)) ## args.name として利用可能
$ ./arg_parse.py --name IHR
Hello, IHR!!
デフォルトで -h, --help を入れるとusageが表示されます。
$ ./arg_parse.py -h
usage: arg_parse.py [-h] [-n NAME]
optional arguments:
-h, --help show this help message and exit
-n NAME, --name NAME
argpaseは git add
, git commit
のようなサブコマンドにも対応しています
#!/usr/bin/env python3.3
import sys
import argparse
def greeting(args):
print("Hello, {name}!!".format(name=args.name))
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subparser_name')
subparser = subparsers.add_parser("greeting")
subparser.add_argument('-n', '--name', default="world")
args = parser.parse_args()
if not args.subparser_name:
parser.print_help()
sys.exit(1)
if args.subparser_name == "greeting":
greeting(args)
実行結果
$ ./arg_parse.py greeting -n ihr
Hello, ihr!!
$ ./arg_parse.py
usage: arg_parse.py [-h] {greeting} ...
positional arguments:
{greeting}
optional arguments:
-h, --help show this help message and exit
argparserとTODOアプリケーションを使ってTODOアプリケーションを作ります
アプリケーションはmain()
で始まることにします。__name__
が__main__
となっているとき、スクリプトが実行されています。
def main():
parser = argparse.ArgumentParser()
parser.add_argument('shelve')
subparsers = parser.add_subparsers()
add_parser = subparsers.add_parser('add')
add_parser.set_defaults(func=cmd_add)
list_parser = subparsers.add_parser('list')
list_parser.add_argument('-a', '--all', action="store_true")
list_parser.set_defaults(func=cmd_list)
finish_parser = subparsers.add_parser('finish')
finish_parser.add_argument('task')
finish_parser.set_defaults(func=cmd_finish)
args = parser.parse_args()
db = shelve.open(args.shelve, 'c')
try:
args.db = db
if hasattr(args, 'func'):
args.func(args)
else:
parser.print_help()
finally:
db.close()
if '__name__' == '__main__':
main()
def cmd_add(args):
name = input('task name:')
due_date = datetime.strptime(input('due date [Y-m-d]:'), '%Y-%m-%d')
required_time = int(input('required time:'))
task = create_task(name, due_date, required_time)
add_task(args.db, task)
def cmd_list(args):
if args.all:
tasks = all_task(args.db)
else:
tasks = unfinished_task(args.db)
for key, tasks in tasks:
print("{0} {1}".format( key, format_task(task)))
def cmd_finish(args):
task = args.db[args.task]
finish_task(task)
args.db[args.task] = task
Pythonは簡単に書くことができるため、ファイル処理やテキスト処理、簡単なデータ管理などの非常に役立ちます。WindowsやMac, Linuxなどのマルチプラットフォームで実行することができるユーティリティはアプリケーション開発の優秀な手助けとなるでしょう