10. 特殊な処理メソッド¶
mcmdのメソッドが行っていることは、バイトストリームとして受け取ったデータに対して
何らかの「処理」を行い、その結果をバイトストリームとして出力しているだけである。
とすると、その「処理」をPythonの関数やOSのコマンドで実現できてもおかしくない。
これらの機能を担うのが以下で説明する、 runfunc
と cmd
メソッドである。
10.1. runfunc: 関数の実行¶
runfunc
メソッドは、
Pythonの関数をあたかもmcmdのメソッドのように動作させるためのメソッドである。
基本的な利用方法を リスト 10.1 に示している。
bigAmount
関数の中で使われている、 mstdin
と mstdout
メソッドがポイントで、
それぞれ、標準入力を読み込む、そして標準出力に書き出す機能を持っている。
このように、関数が、標準入力を入力データとして読み込み、標準出力を出力データとして書き出す機能さえ持っていれば、
runfunc
関数によって、mcmdの処理メソッドのように扱えるようになるのである。
リスト 10.1 では、 mcut
の出力が、
runfunc
により、 bigAmount
関数の標準入力に接続されており、
またbigAmount関数の標準出力が、 msum
へと接続されている。
1import nysol.mcmd as nm 2dat=[ 3["customer","date","amount"], 4["A","20180101",5200], 5["B","20180101",800], 6["B","20180112",3500], 7["A","20180105",2000], 8["B","20180107",4000] 9] 10 11def bigAmount(lowerBound): 12 f = None 13 f <<= nm.mstdin() 14 f <<= nm.mselnum(f="amount",c="[%d,]"%lowerBound) 15 f <<= nm.mstdout() 16 f.run() 17 18sel=None 19sel <<= nm.mcut(f="customer,amount",i=dat) 20sel <<= nm.runfunc(bigAmount,lowerBound=4000) 21sel <<= nm.msum(k="customer",f="amount") 22print(sel.run()) 23# [['A', '5200'], ['B', '4000']]
mcmdを用いずに関数を実装する¶
リスト 10.1 では、mcmdの処理メソッド mstdin
mstdout
をつかって実装したが、
Pythonで利用可能な一般的な標準入出力のライブラリを用いても全く問題ない。
リスト 10.2 は リスト 10.1 の bigAmount
関数のみを書き換えたものである。
中では、sysライブラリの stdin
を標準入力として用い、print で標準出力に書き出している。
全てPythonのネイティブコードのため自由度は非常に高くなる。
ただし、この場合、CSVデータのparsingロジックを自分で書く必要があることに注意する。
単純なCSVであれば問題ないが、ダブルクオーテーションやカンマの入った文字列を扱うのは結構面倒である。
1import sys 2def bigAmount2(lowerBound): 3 header = True 4 for line in sys.stdin: 5 if header: 6 print(line.strip()) 7 header = False 8 else: 9 tokens = line.strip().split(",") 10 if int(tokens[1])>=lowerBound: 11 print(",".join(tokens))
mcmdのイテレータを用いた実装¶
最後に、リスト 10.1 と リスト 10.2 の中間的な書き方として、
リスト 10.3 に示すように、CSVのparsingはmcmdの mstdin
にまかせて、
その後にmcmdのイテレーションを用いてPythonロジックを書く方法を紹介しておこう。
ポイントは、mcmdのイテレータは項目名ヘッダーを無視するため、 for
文の中でヘッダー行を意識しなくてもよい一方で、
次のメソッドへの出力には項目名ヘッダーを出力しなければならないという点である。
以下のコードでは、最初に項目名ヘッダーを出力している。
1def bigAmount(lowerBound): 2 print("customer,amount") 3 for line in nm.mstdin(): 4 if int(line[1])>=lowerBound: 5 print(",".join(line))
もしデータから項目名を取得したければ、 mstdin
に続けて、 getline
イテレータに接続し、
そこで項目名行を出力するオプション header=True
を指定すれば良い。
項目名行の扱いは、 リスト 10.2 と同様である。
1def bigAmount(lowerBound): 2 header = True 3 for line in nm.mstdin().getline(header=True): 4 if header: 5 print(",".join(line)) 6 header = False 7 else: 8 if int(line[1])>=lowerBound: 9 print(",".join(line))
runfuncのデバッグ¶
runfunc()
メソッドは指定された関数をPythonにまかせて実行するだけなので、
もし関数の中でエラーが生じても、エラーが生じたことは分かっても、その詳細については感知していない。
例えば、 リスト 10.5 は、上述の リスト 10.3 に文法エラーを加えたコードで、
これを実行した時のエラーメッセージは リスト 10.6 に示すとおりである。
このように、runfuncでエラーが起こっていることは分かってもそれ以上の詳細はわからない。
1import sys 2import nysol.mcmd as nm 3 4def bigAmount(lowerBound): 5 print("customer,amount") 6 for line in nm.mstdin(): 7 if int(line)>=lowerBound: # lineの要素を指定していないエラー 8 print(",".join(line)) 9 10sel=None 11sel <<= nm.mcut(f="customer,amount",i=dat) 12sel <<= nm.runfunc(bigAmount,lowerBound=4000) 13sel <<= nm.msum(k="customer",f="amount") 14print(f.run(msg="on"))1$ python debug1.py 2#END# kgload; IN=0 OUT=5; 2018/09/05 10:18:51; 2018/09/05 10:18:51 3#END# kgcut f=customer,amount; IN=5 OUT=5; 2018/09/05 10:18:51; 2018/09/05 10:18:51 4#ERROR# error occured in the function, check the detail error message using try-exception in the function. (kgpyfunc); kgpyfunc; ; 2018/09/05 10:18:51; 2018/09/05 10:18:51 5#ERROR# ; kgshell (script RUN KGERROR runmain on kgshell); 2018/09/05 10:18:51 6RuntimeError: runmain on kgshell 7[]
関数の中でのエラーの詳細を追跡するには、 try
〜 exception
を入れることで解決できる。
そのコードを リスト 10.7 に示す。
exception
の中で、トレースバック関数を呼び出しているが、出力先を標準エラー出力にするのがポイントである。
標準入出力は、 runfunc
メソッドによってデータとし扱われてしまうからである。
また、デバッグ目的で変数の内容を表示させるときも、標準出力ではなくエラー出力に出さなければならない。
以下のコードでは、 sys.stderr
のメソッドを使って引数 lowerBound
を標準エラー出力に出力している。
実行時のメッセージは リスト 10.8 に示す通りで、
7行目の int(line)
に問題があることが示され、また最初に lowerBound
の内容も表示されている。
1def bigAmount(lowerBound): 2 try: 3 sys.stderr.write(str(lowerBound)+"\n") 4 print("customer,amount") 5 for line in nm.mstdin(): 6 if int(line)>=lowerBound: 7 print(",".join(line)) 8 except Exception as e: 9 with open('/dev/stderr', 'w') as fpe: 10 traceback.print_exc(file=fpe)1$ python debug2.py 24000 3#END# kgcut f=customer,amount; IN=5 OUT=5; 2018/09/05 10:32:47; 2018/09/05 10:32:47 4#END# kgload; IN=0 OUT=5; 2018/09/05 10:32:47; 2018/09/05 10:32:47 5Traceback (most recent call last): 6File "special_runfunc.py", line 7, in bigAmountBug 7if int(line)>=lowerBound: 8TypeError: int() argument must be a string, a bytes-like object or a number, not 'list' 9#END# kgpyfunc; ; 2018/09/05 10:32:47; 2018/09/05 10:32:47 10#END# kgsum f=amount k=customer; IN=0 OUT=0; 2018/09/05 10:32:47; 2018/09/05 10:32:47 11#END# kgload; IN=0 OUT=0; 2018/09/05 10:32:47; 2018/09/05 10:32:47
runfuncは試験運用¶
runfuncメソッドは非常に強力で、個人や企業がよく利用する処理機能をメソッド化することが可能となり、 プログラムのモジュール化を促進できる。 しかしながら、一方でrunfuncからrunfuncを実行することも可能で、このようなネストが深くなった時にも 内部的にはどうにか頑張って処理しようとするが、まだ十分な運用と検証ができていない。 現在のところ、このメソッドは試験運用と考えてもらいたい。
10.2. cmd: コマンドの実行¶
runfunc
が処理をPythonの関数で実現していた一方で、 cmd
メソッドは、OSのコマンドによって実現するものである。
UNIX系OSの多くは標準入力からデータを受け取り、コマンド内部で一定の処理を付し、標準出力に結果を書き込む。
基本的な利用方法を リスト 10.9 に示している。
ここでは、 customer
と amount
項目を選択したのち、 tr
コマンドに接続している(あまり意味のある例ではない)。
tr
コマンドは 入力のバイトストリームに対して1文字単位の置換を実行する。
以下の例では 文字 A
を C
に置換している。
結果として、顧客 A
が C
に置き換わった集計結果が計算されている。
ただし、cmd
メソッドには、項目名もデータ本体も区別することはなく、
さらにはカンマ区切りの項目も区別なくデータストリームとして流されるだけであることに注意する。
例えば、以下の例では、もし項目名に A
が含まれていれば、それも C
に変換されてしまい、意図した動きにはならない。
項目名ヘッダーの問題だけであれば、 リスト 10.10 に示されるように、直前のメソッド mcut
で項目名ヘッダーを抑制し( nfno=True
)
そして、コマンド実行後に mcut
メソッドにより項目名ヘッダー行を追加してやれば良い。
1import nysol.mcmd as nm 2dat=[ 3["customer","date","amount"], 4["A","20180101",5200], 5["B","20180101",800], 6["B","20180112",3500], 7["A","20180105",2000], 8["B","20180107",4000] 9] 10 11f=None 12f <<= nm.mcut(f="customer,amount",i=dat) 13f <<= nm.cmd("tr 'A' 'C'") 14f <<= nm.msum(k="customer",f="amount") 15print(f.run()) 16# [['B', '8300'], ['C', '7200']]1f=None 2f <<= nm.mcut(f="customer,amount",nfno=True,i=dat) 3f <<= nm.cmd("tr 'A' 'C'") 4f <<= nm.mcut(f="0:customer,1:amount",nfni=True) 5f <<= nm.msum(k="customer",f="amount") 6print(f.run(msg="on")) 7# [['B', '8300'], ['C', '7200']]
ファイル一覧の取得¶
UNIX系OSには多くの便利なコマンドが多く存在する。
例えば、 表構造データを柔軟に扱うawk、パターンマッチで行を選択するgrep、
正規表現による文字列置換のsedなどである。
UNIX系のコマンドの扱いに慣れた人にとっては、cmdメソッドを利用することで、
これらのコマンドをmcmdメソッドと連携して利用することができるようになる。
以下に、 ls
tail
sed
の3つのコマンドを用いて、ファイルリストの一覧を処理するプログラムを紹介しておく。
リスト 10.11 はそのコードである。
ls -l
でパーミッションやサイズ、ファイル名といった情報が標準出力に出力される。
最初の行にファイル数の情報が出力されるので tail
コマンドでその行をスキップしている(2行目から読み込む)。
そして、 ls
の出力の区切り文字である複数のスペース文字を sed
コマンドによりカンマに変換している。
あとは、 mcut
メソッドで項目名ヘッダーを付けてファイル一覧の出来上がりである。
1f=None 2f <<= nm.cmd("ls -l") 3f <<= nm.cmd("tail +2") 4f <<= nm.cmd("sed 's/ */,/g'") 5f <<= nm.mcut(nfni=True,f="0:permission,1:link,2:user,3:group,4:volume,5:month,6:day,7:time,8:filename") 6print(f.run()) 7# [['-rw-r--r--', '1', 'foo', 'staff', '4997', '8', '3', '16:44', 'dat.csv'], ['-rw-r--r--', '1', 'foo', 'staff', '104', '9', '6', '10:56', 'dat2.csv'], ...]
マルチバイト文字の変換¶
最後に、データクリーニングでよく利用されるマルチバイトコードの変換コマンドである nkf の利用例を
リスト 10.12 に示しておく。
ただし、これは動作させるためのコーディング例であるため、データ dat.csv
にはマルチバイト文字は含まれていないが、
このファイルがShift_jisコードであることを想定している。
また、OSコマンドとして nkf をインストールしておく必要がある。
1>>> import nysol.mcmd as nm 2>>> with open('dat.csv','w') as f: 3>>> f.write( 4'''customer,quantity,amount 5A,20180101,5200 6B,20180101,800 7B,20180112,3500 8A,20180105,2000 9B,20180107,4000 10''') 11 12>>> f=None 13>>> f <<= nm.cmd("nkf -Sw dat.csv") 14>>> f <<= nm.mcut(f="customer,amount") 15>>> f <<= nm.msum(k="customer",f="amount") 16>>> print(f.run()) 17[['A', '7200'], ['B', '8300']]