pythonでlogging

pythonでlogging

ツール的に使っているpythonだが、処理速度を上げるためにマルチスレッド化させたり、ツール規模が大きくなってモジュール化させたり、少し凝った事をすると、printでの実行ログ出力では物足りなくなる。
pythonではloggingの仕組みも標準で組み込まれている。

準備

1
2
import logging.config
logging.config.fileConfig('logging.conf')

logging.confにログ出力設定を書いておき、loggingを初期化する。
初期化とは、ログの出力情報(出力先、出力フォーマットなど)を設定すること。
logging.confの内容は下記の通り

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[loggers]
keys=root

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('Log.txt', 'w', 'utf-8', False)

[formatter_simpleFormatter]
format=%(asctime)s [%(levelname)-5s] (%(module)s).(%(funcName)s).(%(lineno)d) %(message)s

コンソールとファイルにログを出力している。

もっとシンプルに初期化したい時は、logging.basicConfigを呼び初期化する。

1
2
3
4
5
import logging
logging.basicConfig(
    format='%(asctime)s [%(levelname)-7s] (%(funcName)s) %(message)s',
    level='INFO',
)

コンソールに処理時間とログレベルと関数名、メッセージを表示する。
処理時間が表示されるだけでも、便利なことも多い。
関数名が表示されるので、処理状況を確認しやすい。

ログの出力

1
2
3
4
logging.debug('debug message.')
logging.info('information message.')
logging.warn('warning message.')
logging.error('error message.')

loggingに用意されたショートカットメソッド(info,errorなど)で簡単にログを出力できる。
ちなみに、上記の準備ではログレベルをINFOにしているため、ログ出力の例のdebug message.は出力されない。

各モジュールでのlogの実装

各モジュールでは、loggerを生成し、
そのloggerでメッセージを出力する。

1
2
3
def another_function():
    logger = logging.getLogger(<strong>name</strong>)
    logger.info('process start!')

このように実装しておくことで、呼び出し元がログ出力を制御できる。
出力先を変えたり、出力すべきログレベルを変更したり、
それ用のhandlerを用意したりできる。

同じタグの記事
同じカテゴリの記事

pythonで設定ファイル

pythonで設定ファイルを使う

サーバ名やユーザID・パスワード、参照先のパスなど、オンコーディングしたくない値がどうしてもある。そんなとき、pythonではiniファイル形式の設定ファイルを簡単に扱えるモジュールが標準機能で準備してある。

実装例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import ConfigParser
'python3ではimport configparser'

'''設定ファイルの作成'''
s_config = ConfigParser.ConfigParser()
'python3ではs_config = configparser.ConfigParser()'
s_config.add_section('Section1')
s_config.set('Section1', 'Key1', 'Value1')
s_config.set('Section1', 'Key2', 'Value2')
s_config.add_section('Section2')
s_config.set('Section2', 'Key3', 'Value4')
s_config.set('Section2', 'Key4', 'Value4')
with open('Config.ini', 'wb') as w:
    s_config.write(w)

'''設定ファイルの読み込み'''
r_config = ConfigParser.ConfigParser()
r_config.read('Config.ini')
print(r_config.get('Section1', 'Key1'))
print(r_config.get('Section2', 'Key4'))

このサンプルでは、iniファイルの作成もプログラムで行なっているが、iniファイル形式のファイルなので、テキストエディタで直接、設定ファイルを作成しても問題ない。

ちなみに、上記の例で作成されるConfig.iniは下記の通り。

[Section1]
key1 = Value1
key2 = Value2

[Section2]
key3 = Value4
key4 = Value4
同じタグの記事
同じカテゴリの記事

pythonでftp取得自動化

pythonでftp取得自動化

短期間のバックアップ目的で、毎日FTP受信しているファイル群がある。
サイズも小さいし、それほど手間ではないので手作業を実施していたが、
やはり面倒になってきたので、pythonスクリプトを作成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python

import os
import logging
import ftplib

local_dat_dir = 'data/'
server_dat_dir = '/data_dir'

print('start.')

''' ダウンロード済みのファイルリスト作成 '''
for f in os.listdir(local_dat_dir):
    exists_list.append(f)

''' ダウンロードされていないファイルのダウンロード '''
try:
    ''' FTP接続し、サーバのデータディレクトリに移動 '''
    ftp = ftplib.FTP('ftp-server')
    ftp.login('ftp-user', 'ftp-password')
    ftp.cwd(dat_dir)
    ''' サーバに存在するファイルでループ '''
    for path in ftp.nlst(server_dat_dir):
        ''' パスを除去する
        filename = path[len(server_dat_dir) + 1:]
        '''
ダウンロードされていない場合は、ダウンロードする。 '''
        if not filename in exists_list:
            print('ftp get start. [{}]'.format(filename))
            # ダウンロード
            with open('{}/{}'.format(local_dat_dir, f), 'wb') as w:
                ftp.retrbinary('RETR {}'.format(f), w.write)
            print('ftp get end.')
finally:
    '''
FTP切断 '''
    ftp.quit()
print('end.')

こんな簡単なスクリプトでFTP受信できるのであれば、もっと早く作るべきだった。

同じタグの記事
同じカテゴリの記事

pythonでテンプレートエンジンを利用する

pythonでテンプレートエンジンを利用する

バッテリー同梱のpythonはテンプレートエンジンも標準に組み込まれている。
下記の通り利用することができる。

1
2
3
import string
t = string.Template('Hello ${name}!')
t.substitute({'name': 'Python'})

しかし、このテンプレートはとてもシンプルな変換しかできない。
単純なキーワード置換のみ。

そこで簡単に導入できて、ループや条件分岐ができるテンプレートを利用する。

bottle

https://bottlepy.org/docs/dev/

テンプレートエンジンは、bottleの一部の機能。
それでも、十分に高性能なテンプレートが利用できる。

インラインで変換

1
2
3
4
5
6
7
8
9
10
11
12
13
import bottle
bottle.template('''
    {{ msg }}
    % for item in items:
        {{ item['no'] }} : {{ item['name'] }}
    % end
'''
, {
    'msg': 'test bottle',
    'items': (
        {'no': '1', 'name': 'AAA'},
        {'no': '2', 'name': 'BBB'},
    )
})

ファイルのテンプレートを読んで、ファイルに出力する

template.tmpl

1
2
3
4
{{ msg }}
% for item in items:
    {{ item['no'] }} : {{ item['name'] }}
% end
1
2
3
4
5
6
import bottle
import codecs
data = {}
with codecs.open('output.txt', 'w', 'utf-8') as w:
    with codecs.open('template.tmpl', 'r', 'utf-8') as r:
        w.write(bottle.template(r.read(), data))

まとめ

bottleを使うと、一般的なテンプレート機能が使える。
bottleは%の行の変換が適切に行われるので、メールなどのテキストテンプレートに問題なく使える(テンプレートエンジンによっては、余分な改行やスペースが入ってしまったりする)。
また、テンプレートエンジンはbottleの一つの機能でしかなく、もっといろいろなことができる。
スタンドアロンサーバになったりするようだ。
これらも活用できれば、ツールの幅がぐっと広がりそうだ。

同じタグの記事
同じカテゴリの記事

pythonでFTP

pythonでFTPする

サーバ間のファイル転送など、保守作業で安全が確保されている場合にFTPを利用している。
保守作業も定期的な保守となると、pythonスクリプトが役に立つ。

FTPサーバに接続

1
2
3
import ftplib
ftp = ftplib.FTP('ftp-server')
ftp.login('ftp-user', 'ftp-password')

ftp-server,ftp-user,ftp-passwordは適宜書き換える。

FTPサーバへファイルを転送

1
2
3
4
5
'カレントディレクトリを移動'
ftp.cwd('/path/to/data_dir')
'ファイルを開き転送'
with open('target_file.dat', 'rb') as rb:
    ftp.storbinary('STOR target_file.dat', rb)

クライアントファイルは、openの引数で深いディレクトリでもディレクトリを移動せずに指定できる。
サーバファイルは、cwdにディレクトリを移動してから個別に転送する。

FTPサーバからファイルを取得

1
2
3
4
5
'カレントディレクトリを移動'
ftp.cwd('/path/to/data_dir')
'ファイルを取得し保存'
with open('target_file.dat', 'wb') as wb:
    ftp.retrbinary('RETR target_file.dat', wb.write)

サーバファイルは、cwdでディレクトリを移動してから個別に取得する。
クライアントファイルは、openの引数で深いディレクトリでもディレクトリを移動せずに指定できる。

FTPサーバから切断

1
ftp.quit()

まとめ

ftpコマンドにテキストファイルを渡してバッチ処理してもよいが、Pythonで作った方が応用が効いて良いと思う。

2018.9.15 FTP送信のコマンドが間違っていました。FTP送信はRETRではなくSTORが正解です。
mae8bitさん、ご指摘ありとうございました。

同じタグの記事
同じカテゴリの記事

jythonのヒープメモリ設定

jythonのヒープメモリ設定

jythonプログラムがOutOfMemoryErrorで落ちてしまったとき、その原因はヒープメモリが不足していることが原因だと考えられる。
もっとも、それほどメモリを使ってしまうプログラムが問題かもしれないが、今時のPCならpythonにギガ単位のメモリを使わせても、プログラム自体が簡略化できるのであれば、それもありだと思う。

ヒープメモリ設定方法

現在の割当容量を確認する

1
2
3
jython --print
C:> C:\usr\java\bin\java -Xmx512m -Xss1024k -classpath C:\usr\jython\jython.jar; -Dpython.home=C:\usr\
jython -Dpython.executable=C:\usr\jython\bin\jython.exe -Dpython.launcher.uname=windows -Dpython.launcher.tty=true org.python.util.jython

-Xmx512mより、ヒープサイズが512MBであることが確認できる。

-Xmxを1024MBに拡張

Windows

1
SET JAVA_MEM=-Xmx1024m

Linux Bash

1
export JAVA_MEM=-Xmx1024m

改めて割当容量を確認する

1
2
3
jython --print
C:> C:\usr\java\bin\java -Xmx1024m -Xss1024k -classpath C:\usr\jython\jython.jar; -Dpython.home=C:\usr\
jython -Dpython.executable=C:\usr\jython\bin\jython.exe -Dpython.launcher.uname=windows -Dpython.launcher.tty=true org.python.util.jython
同じタグの記事
同じカテゴリの記事

pythonのバージョン選び

pyhtonのバージョン選び

本当なら、迷わずpython3を選ぶべきなんだろうけど、
置かれている仕事の状況では、python3がベストとならない事があった。
なので、未だにpython2を使うことがよくある。

python2を使う場面

javaで実装されたpythonことjythonを使う場合。

他システムのデータを取り出すときjavaのAPIを呼び出すため、jythonを利用している。
しかし、jythonはpythonのバージョンが2.7.0なので、python2としてコーディングする必要がある。

余談だが、jythonでのコーディングでpythonのバージョン以外で気になることろは

  • sqlite3モジュールがない。(sqlite.jarを使ってJDBC接続すればsqlite3に接続できる)
  • JavaのStringクラスとpythonの文字列の自動変換あたりで思わぬ動作をすることがある。(文字列の結合で失敗することがある。StringBuilderなどを用いて結合すれば問題ない)

サーバーのpythonのバージョンが2.6

利用しているredhatサーバのpythonのバージョンが2.6。サーバのモジュールのバージョン更新はなかなかできないので、2.6のまま利用している。

余談だが、python2.6でコーディングしていて気になるてん

  • ”.format()でフォーマットの中括弧の中の番号を省略できない。(‘{},{}’.format(a,b)が利用できない。ちゃんと'{0},{1}’.format(a,b)という風に、数字を指定する必要がある)

共有端末のmacのpythonがバージョン2.7

動作確認用macで動作するpythonのバージョンが2.7。動作確認用端末なので設定を変更することができない。

python2で問題があるか

python2でも、特に問題はない。
コンパイルエラーは標準ライブラリのパッケージ名が異なることくらい。
実行時エラーは、文字列関連でラフに処理しているところは、strとunicodeの違いでエラーになることがある。

サラッと書いた、このstrとunicodeの違いは、気にするのが面倒なところなので、ここを考えなくて良いだけでもpython3を使うメリットは十分にあると思う。

同じタグの記事
同じカテゴリの記事

Windowsでpython3 ZIPでダウンロードした場合

Windowsでpython3。ZIPでダウンロードした場合

なんらかの事情がって、pythonをインストーラーでなく、ZIPファイルをダウンロードして使う場合、準備が必要。

ZIPを解凍したディレクトリのpython36._pthのファイル名を変更

ZIPを解凍したディレクトリに保存されているpython36._pthのファイル名を変更する。
python36._pth → python36.pth

これを実行しないと、インタラクティブシェルを利用したときに、quit()が利用できない。
pythonの教本には普通に書いてあることができないので、いきなり躓いてしまう。

quit()が利用できないとき

ファイル名変更前にインタラクティブシェルに入ってしまった場合、quit()を利用できない。
そんなときはsysをimportしてexit(0)すればよい。

1
2
import sys
sys.exit(0)
同じタグの記事
同じカテゴリの記事

pythonでマルチスレッドプログラミング

pythonでマルチスレッドプログラミング

pythonは簡単にマルチスレッドで動作するプログラムを簡単に作れる。
javaだと、それ用にクラスを作成する必要があるが、pythonでは関数をマルチスレッドで動かすことができる。
ちょっと作ってみたツールが、性能的に問題がある場合、該当処理を関数に切り出し、それをマルチスレッドで動かせる。

とりあえずプログラムの全貌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python

import threading
import queue
import logging

def worker(q):
    '''pythonでキューを多重処理するスレッド'''
    while True:
        item = q.get()
        if item is None:
            break
        logging.info(item)

'''どのスレッドで処理されているかどうか見たいので、logging機能を使ってログ出力する'''
logging.basicConfig(
    format = '%(asctime)s [%(levelname)-5s] [%(thread)5d] (%(name)s) %(message)s',
    level = 'INFO')
'''開始メッセージを出力'''
logging.info('start')
'''処理キューを登録するためのqueueを作成する'''
q = queue.Queue()
'''キューを処理するスレッドを準備する'''
threads = []
for i in range(20):
    t = threading.Thread(target = worker, args = (q, ))
    t.start()
    threads.append(t)
'''キューにタスクを登録する'''
for item in range(100):
    q.put(item)
'''スレッドの数だけ、キュー終端のNoneをキューに登録する'''
for t in threads:
    q.put(None)
'''スレッドが終了することを待ち合わせる'''
for t in threads:
    t.join()
'''終了メッセージを出力'''
logging.info('end')

処理をする関数を作成する

1
2
3
4
5
6
7
def worker(q):
    '''pythonでキューを多重処理するスレッド'''
    while True:
        item = q.get()
        if item is None:
            break
        logging.info(item)

while Trueの無限ループを作成しておき、queueからキューを1つポップする。
キューからポップした値がNoneのときはループを抜ける。
キューの最後にNoneを入れるのは、後のキュー登録の後処理で行う。

検証用にloggingを準備する

1
2
 [%(thread)5d] (%(name)s) %(message)s',
    level = '
INFO')

loggingを利用するとログにスレッドのIDを出力できるようになる。
これを利用して、マルチスレッドで処理されていることを確認する。

空のキューを用意し、マルチスレッドで動作させるスレッドを用意する

1
2
3
4
5
6
7
q = queue.Queue()
'''キューを処理するスレッドを準備する'''
threads = []
for i in range(20):
    t = threading.Thread(target = worker, args = (q, ))
    t.start()
    threads.append(t)

先に空のキューを作成しておく。
そのキューを引数に取る、マルチスレッドで実行するスレッドを作成する。
実行したスレッドは、後で処理の待ち合わせをするために、リストに保存しておく。

キューにタスクを登録する

1
2
for item in range(100):
    q.put(item)

このサンプルではシンプルな数値を設定しているが、キューにはなんでも登録できるので、
DBから取得したデータや、DTOなど、なんでも自由に設定できる。

キューの終端を登録する

1
2
for t in threads:
    q.put(None)

マルチスレッドで実行される関数は、キューからNoneを取得した時点でスレッドを終了するように定義している。
なので、キューに実行すべきキューを登録した後に、スレッドの数だけ終端の合図(None)を設定しておく。

各スレッドが終了することを確認する

1
2
for t in threads:
    t.join()

スレッドのjoin()は、スレッドが完了したときに戻ってくる。
これを利用して、すべてのスレッドのjoin()が完了するまで処理を待つ。

まとめ

これらの応用することで、簡単にマルチスレッドで処理することができる。
私の場合は、処理は他のシステムのAPIを呼ぶことが多い。
他のシステムAPIがネックで、待ちが多く発生しているときなどは、その部分をマルチスレッドにし、
幾つか多重でリクエストをかけている。
同じデータを複数処理してももったいないので、キューに一度に一気に溜め込んでいる。
また、結果出力はsqliteを使うことが多いが、sqlite3はマルチに書き込むと思わぬエラーが発生することがあるので、
sqliteへの書き込みもスレッド化(シングル)し、各マルチ処理は書き込みキューへキューイングすることで実装している。

javaで作ればいくつもクラスを作って冗長になりがちな実装が、pythonだと1ファイルで簡単に作成できてしまう。

同じタグの記事
同じカテゴリの記事

バッチ処理の基本・ファイル入出力

バッチ処理の基本といえば、ファイル入出力。
最近は、DBアクセスが基本となっているが、システム間連携などは、未だにファイル連携が多い。
連携されてきたファイルを直接取り込んでも良いけど、あまりにも格好悪いファイルは、事前にある程度整えておきたい。
そんな時に、バッチ処理が役立つ。
私がよく使い言語は、下記の4つ。それぞれの言語で、1つのファイルを読んで、1つのファイルを出力する雛形を置いておく。

1.pythonの場合(最近は、pythonでチョチョっと加工することが多いかも)

1
2
3
4
5
6
import codecs
with codecs.open('in.txt', 'r', 'utf_8') as r, codecs.open('out.txt', 'w', 'utf_8') as w:
    counter = 0
    for line in r:
        counter += 1
        w.write('{:03d}:{}'.format(counter, line))

とても簡潔に、描きたいロジックだけを書くことができます。

2.javaの場合(Javaで作らないと実行の承認が通らない時はJavaで作ります)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.lang.String;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.BufferedReader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;

public class FileIo {
    public static void main(String[] args) {
        try (
            BufferedReader r = Files.newBufferedReader(
                    Paths.get("in.txt"), StandardCharsets.UTF_8);
            Writer w = Files.newBufferedWriter(
                    Paths.get("out.txt"), StandardCharsets.UTF_8,
                    StandardOpenOption.WRITE,
                    StandardOpenOption.APPEND,
                    StandardOpenOption.CREATE)
        ) {
            String line = null;
            int counter = 0;
            while((line = r.readLine()) != null) {
                counter++;
                w.write(String.format("%03d:%s\n", counter, line));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

昔に比べれば、だいぶシンプルに書けるようにはなったけど、冗長な感じがする。
しかし、実行速度は結構速い。Java7以降は性能をほとんど気にせずコーディングしても、それなりのスピードで動いてくれる。

3.C#の場合(実行環境がWindowsの場合はC#で作ればだいたい動く)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using System.Text;
using System.IO;

public class FileIo
{
    public static void Main()
    {
        try
        {
            using(StreamReader r = new StreamReader("in.txt", Encoding.GetEncoding("UTF-8")))
            using(StreamWriter w = new StreamWriter("out.txt", false, Encoding.GetEncoding("UTF-8")))
            {
                int counter = 0;
                while(r.Peek() >= 0)
                {
                    counter++;
                    w.WriteLine(string.Format("{0:000}:{1}", counter, r.ReadLine()));
                }
            }
        }
        catch (Exception e)
        {
            Console.Write(e.StackTrace);
        }
    }
}

Visual Studioがなくても、気合いでC#を簡単なテキストエディタでコーディングして、
.Net Framework付属のcscでコンパイルし実行できます。
特別な環境がなくても動くあたりは素晴らしい。

4.perlの場合(ファイル入出力はやっぱり速い。Pythonでは性能が出ない時はperlで実装)

1
2
3
4
5
6
7
8
9
10
use strict;
open my $r, '<:encoding(utf8)', 'in.txt';
open my $w, '>:encoding(utf8)', 'out.txt';
my $counter = 0;
while (<$r>) {
    $counter++;
    printf $w "%03d:%s", $counter, $_;
}
close $r;
close $w;

perlもシンプルに書けて良い。ただし、最近のredhatはperlがインストールされていない
こともあるので、必ずインストールされているPythonでツールを作ることが多くなった。
perl実行環境は、WindowsでもLinuxでもOracleがインストールされていると、その中に大概入っている。
そのperl実行環境を利用するのも良い。Oracleインストール領域に入っているので、perlからOracleに接続してDB操作するのも簡単。

python,java,c#はcloseの心配がない構文が用意されているのが良い。
perlはcloseを明示的に行う必要がある。
python,perlは目的のロジックに集中してコーディングができる。
Java,C#は定義部分が多くて。
VisualStudioやEclisepに頼って簡単にコーディングできたとしても、
メイン処理以外が多いのは、後から読んだ人にとってもメンテナンスしづらいかも。

同じタグの記事
同じカテゴリの記事