Delphiでインターフェースを使う

久々にDelphiを少し触ってました。かなり忘れてる。
Delphiのインターフェースを使って2つの実装クラスを作り、実行時にどちらのクラスを使うか決定してる。
試したバージョンはDelphi XE4。

Main.dpr

program Main;
{$APPTYPE CONSOLE}

uses
  System.Classes,
  System.SysUtils,
  System.Generics.Collections;

type
  IStore = interface
  { データを読み書きするインターフェース }
    procedure SetValue(Key, Value: String);
    function GetValue(Key: String): String;
  end;

  TStore = class(TInterfacedObject, IStore)
  { データを読み書きするベースクラス }
  public
    procedure SetValue(Key, Value: String); virtual; abstract;
    function GetValue(Key: String): String; virtual; abstract;
  end;

  TMemoryStore = class(TStore)
  { データをメモリ上にのみ保持するクラス }
  private
    StoredData: TDictionary<String, String>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetValue(Key, Value: String); override;
    function GetValue(Key: String): String; override;
  end;

  TFileStore = class(TStore)
  { データをファイルに保持するクラス }
  private
    Loaded: Boolean;
    FilePath: String;
    StoredData: TDictionary<String, String>;
    procedure Load;
    procedure Save;
  public
    constructor Create(FilePath: String);
    destructor Destroy; override;
    procedure SetValue(Key, Value: String); override;
    function GetValue(Key: String): String; override;
  end;

constructor TMemoryStore.Create;
begin
  WriteLn('-- TMemoryStore --');
  StoredData := TDictionary<String, String>.Create;
end;

destructor TMemoryStore.Destroy;
begin
  StoredData.Free;
end;

procedure TMemoryStore.SetValue(Key, Value: String);
begin
  StoredData.AddOrSetValue(Key, Value);
end;

function TMemoryStore.GetValue(Key: String): String;
begin
  if StoredData.ContainsKey(Key) then
    Result := StoredData.Items[Key]
  else
    Result := '';
end;

constructor TFileStore.Create(FilePath: String);
begin
  WriteLn('-- TFileStore --');
  Loaded := False;
  Self.FilePath := FilePath;
  StoredData := TDictionary<String, String>.Create;
end;

destructor TFileStore.Destroy;
begin
  Save;
  StoredData.Free;
end;

procedure TFileStore.SetValue(Key, Value: String);
begin
  { データベースの遅延読み込み }
  if not Loaded then Load;
  StoredData.AddOrSetValue(Key, Value);
end;

function TFileStore.GetValue(Key: String): String;
begin
  { データベースの遅延読み込み }
  if not Loaded then Load;
  if StoredData.ContainsKey(Key) then
    Result := StoredData.Items[Key]
  else
    Result := '';
end;

procedure TFileStore.Load;
var
  Buffer: TStringList;
  Index: Integer;
  KeyValuePair: TArray<String>;
begin
  Buffer := TStringList.Create;
  if FileExists(FilePath) then
    try
      WriteLn('Loading database...');
      Buffer.LoadFromFile(FilePath);
      for Index := 0 to Buffer.Count - 1 do
      begin
        KeyValuePair := Buffer[Index].Split([','], 2);
        StoredData.AddOrSetValue(KeyValuePair[0], KeyValuePair[1]);
      end;
    finally
      Buffer.Free;
    end;
  Loaded := True;
end;

procedure TFileStore.Save;
var
  Buffer: TStringList;
  Key: String;
begin
  Buffer := TStringList.Create;
  try
    for Key in StoredData.Keys do
    begin
      Buffer.Add(Key + ',' + StoredData.Items[Key]);
    end;
    Buffer.SaveToFile(FilePath);
  finally
    Buffer.Free;
  end;
end;

function StoreFactory(FilePath: String = ''): IStore;
begin
  { FilePathが指定されているならTFileStore、なければTMemoryStoreを使います }
  if FilePath <> '' then
    Result := TFileStore.Create(FilePath)
  else
    Result := TMemoryStore.Create;
end;

var
  Store: IStore;

begin
  { メモリリーク検出 }
  ReportMemoryLeaksOnShutdown := True;

  { コマンドライン引数の有無で実装を切り替える }
  if ParamCount > 0 then
    Store := StoreFactory(ParamStr(1))
  else
    Store := StoreFactory;

  { 値の書き込みと取得 }
  Store.SetValue('MyKey', 'MyValue');
  WriteLn(Store.GetValue('MyKey'));
end.

実行結果

C:\MyApp>dcc32 Main.dpr
Embarcadero Delphi for Win32 compiler version 25.0
Copyright (c) 1983,2013 Embarcadero Technologies, Inc.
Main.dpr(169)
170 行, 0.14 秒, コード 919344 バイト, データ 35392 バイト

C:\MyApp>Main
-- TMemoryStore --
MyValue

C:\MyApp>Main database.txt
-- TFileStore --
Loading database...
MyValue

外部入出力の部分は実装の切り替えができると、自動テストのときに便利です。DIコンテナは大仰だなと思ったら、このぐらいでもいいかもしれませんね。
また、TStoreはTInterfacedObjectを継承しているので、明示的にFreeを呼ばなくても、開放されるようにしています。

通信料金の計算2015年秋

通信料金がいくらぐらいかかっているのか計算などやってたので、メモを残す。

現状の使い方

  • auガラケー、Nexus4、PocketWifi(GL10P)の3台を持ち歩いてる
  • Nexus4はデータ通信専用、SORACOM AirのSIMを挿してるけど、できるだけPocketWifiのほうを使ってる。
  • b-mobileのSIMをしばらく使っていたけど、期限が切れたタイミングでSORACOM Airに変更した。

通信料金

  • au使用料(ガラケー) 2060円/月
  • ワイモバイル(PocketWifi) 4535円/月
  • SORACOM Air(Nexus4/s1.standard) 約500円/月

合計: 7095円/月

感想

auガラケーは滅多に通話しないので安い。
ワイモバイルがやっぱりちょっと高いので、同等のもので安く抑える方法を模索したい。

dulwichを使ってPythonでGitのカスタムコマンドを作る

Gitのコマンドがわかりづらくて好きになれないんですが、気に入らない部分はカスタムコマンドを作ればいいよね。
ということで方法を調べてました。

カスタムコマンドの作り方

Gitのカスタムコマンドは、「git-コマンド名」のような実行可能ファイルを用意して、パスが通ってる場所に置けば有効になるようです。
実行可能であればシェルスクリプトでも問題ないようで、git-flowなどはシェルスクリプトで実装されていますね。

Pythonでdulwichを使ってみる

dulwichはGitリポジトリPythonで読み書きするためのモジュールです。
Dulwich
Pythonでdulwichを使って実行可能なスクリプトを書くことで、カスタムのGitコマンドをPythonで実装する、というのをやってみます。
試したバージョンは、dulwich 0.11.2, Python 3.4。
dulwichのインストールはpipでvirtualenvにでも入れておけばよいかと。
dulwichの使い方はドキュメントのチュートリアルが参考になります。
Tutorial — dulwich 0.14.0 documentation
以下のようにgit-helloという名前のファイルを作成します。
git-hello:

#!/usr/bin/env python
import os
import itertools

from dulwich.repo import Repo


def decode(b):
    return b.decode('utf8')


def main():
    print("----- My Git Command! -----")
    repo = Repo(os.getcwd())
    # Walkerを使ってコミットを取得し、id, author, messageを表示する
    for entry in itertools.islice(repo.get_walker(), 10):
        print("{}: {}, {}".format(
            decode(entry.commit.id),
            decode(entry.commit.author),
            decode(entry.commit.message).strip()))


if __name__ == '__main__':
    main()

先頭のshebangの部分は、環境に応じて変更すると良いかと思います(virtualenvを使っているなら、virtualenv内のpythonにするとか)
これを適当なディレクトリに置いてパスを通します。今回は $HOME/bin に置きました。

(venv)tokibito@ubuntu:~$ ls -l bin
合計 8
-rwxrwxr-x 1 tokibito tokibito  471 11月  5 23:34 git-hello
drwxrwxr-x 5 tokibito tokibito 4096 11月  5 23:07 venv
(venv)tokibito@ubuntu:~$ export PATH=$PATH:$HOME/bin

gitリポジトリになっているディレクトリで実行してみると、最近の10件のコミットが表示されました。
試したのは django-ftpserver のローカルリポジトリ

(venv)tokibito@ubuntu:~/github/django-ftpserver$ git hello
----- My Git Command! -----
a2fa94d4e594d25a9b28203b3a82e5f47b43d67d: Shinya Okano <tokibito@gmail.com>, fixes PEP8, and update version 0.3.2
a8fa3f773714c8b80d8e65cc42c2e5add2b70724: Shinya Okano <tokibito@gmail.com>, Merge pull request #7 from akoumjian/master

merged: Custom Authorizer and Handler classes via settings
659e0eb3204a56a56d0946a9ee880a9adb259cc5: Shinya Okano <shinya.okano@beproud.jp>, added: docker settings for test env
9df037ba02770e1bdd0be8e23ed024031a842948: Alec Koumjian <akoumjian@gmail.com>, allow custom authorizers and handlers

custom tls

add note in docs
8f63fcb3873aa2e9f85870e84a05cf30fc26bb85: Shinya Okano <tokibito@gmail.com>, modify document config
0189b075a633717b27443449558ed6ceda77dbdc: Shinya Okano <tokibito@gmail.com>, update library for documentation
0bc3875f6649db4a0d4234c20a9a14360667e651: Shinya Okano <tokibito@gmail.com>, update version 0.3.1
34d5ef6dc168e43010e4a2e80c346c097671caa7: Shinya Okano <tokibito@gmail.com>, flake8
9c9437f30336abb779871f78ce7d0f39a5e90b17: Shinya Okano <tokibito@gmail.com>, update travis config
664fd42ef583b4f5f2c6ac26827e11ea4f50a4c3: Shinya Okano <tokibito@gmail.com>, modify setup test

こんな感じで、Repoオブジェクトからいろいろ辿れるので、やりたい放題ですね。

A5:SQL Mk-2が便利だった

A5:SQL Mk-2はER図の作成やSQLの実行、結果の表示などができるGUIのツール。Windows用。
ちょっとしたSQLを書くときや、ER図作成のためにWindows用のツールがほしくて、探してたところ見つけた。
しばらく使ってみて、そこそこいい感じだったので紹介する。
A5:SQL Mk-2 - フリーの汎用SQL開発ツール/ER図ツール
MySQLPostgreSQL、SQLite3、OracleSQLServerなど、自分が使うことがありそうなデータベースには一通り対応しているようだ。
PostgreSQLMySQLとSQLite3で試しているが、DDL生成で一部うまくいかない部分があったけど、データを参照したり追加したりなどの操作では概ね問題ない感じ。

ポータブルで動かす機能や、読み込み専用版が用意されているなど、調査業務でも役に立ちそう。

PyPy3 2.4.0を使ってみた

最近、仕事でPyPy3を試したりしているので、メモを書き残しておく。
PyPy - Welcome to PyPy

インストール

今回はUbuntu 14.04LTSで試した。PyPy3はPPAリポジトリに無いので、Linux向けのコンパイル済みバイナリをダウンロードして使うことにした。
利用するときは、virtualenvでpypy3用の環境を作っている。

$ wget https://bitbucket.org/pypy/pypy/downloads/pypy3-2.4.0-linux64.tar.bz2
$ bzip2 -dc pypy3-2.4.0-linux64.tar.bz2|tar xf -
$ sudo mv pypy3-2.4.0-linux64 /opt/
$ sudo ln -s /opt/pypy3-2.4.0-linux64/bin/pypy3 /usr/bin/pypy3

virtualenvで利用する際には --python=pypy3 と指定する。

$ virtualenv --python=pypy3 venv-pypy

互換性について

PyPy3 2.4.0は、CPython 3.2.5互換とのことなので、CPython 3.3以降で導入されたシンタックスが使えないところは気をつけないといけない。
例えば、文字列リテラルに「u」をつけれない点。だたし、これは最初からPython3向けで書いていたコードならそもそも「u」は不要だし、気にしなくてもよさそう。
C言語で書かれたサードパーティ製のモジュールは動かないものが多いので、その場合はPure Pythonのもので代用する必要がある。
今回PyPyを適用したコードは、Python3.4向けに書いていたものであったが、アプリケーションロジックは一行も変更せずに動かすことができた。
互換性は高いと思う。
PyPyを適用するにあたって一点だけハマったところがあった。
「virtualenvでpypy3を動かした際に、出力のリダイレクトを指定するとエンコーディングがasciiになってしまう」というもの。

(venv-pypy)tokibito@ubuntu:~$ python -V
Python 3.2.5 (b2091e973da6, Oct 19 2014, 18:29:55)
[PyPy 2.4.0 with GCC 4.6.3]
(venv-pypy)tokibito@ubuntu:~$ cat test.py
import sys

sys.stdout.write(sys.stdout.encoding + '\n')
sys.stdout.write("日本語\n")
(venv-pypy)tokibito@ubuntu:~$ python test.py > hoge
Traceback (most recent call last):
  File "test.py", line 4, in <module>
    sys.stdout.write("日本語\n")
  File "/home/tokibito/venv-pypy/lib-python/3/encodings/ascii.py", line 22, in encode
    return codecs.ascii_encode(input, self.errors)[0]
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
(venv-pypy)tokibito@ubuntu:~$ cat hoge
ascii

環境変数のPYTHONIOENCODINGにUTF-8を指定することで回避はできたけど、原因は特定できていない。
virtualenvを使わないでpypy3やpython3.4コマンドで実行すると、特に問題は発生しなかった。

パフォーマンスについて

今回PyPyを適用したコードは、ボトルネックが「2重ループ内での巨大なリストに対する値の参照と比較」となっていたバッチ処理
データベースの参照やファイル読み込みについては、ループに入る前にすべて読み込んでキャッシュ済みの状態。
データベース接続と問い合わせはDjangoフレームワークのものを使っている。(Django1.8+pymysql)
PyPyを試す前に、cProfileを使って明らかに遅い処理は修正済みの状態。
Python3.4で実行した場合におよそ30分かかっていたもの。

(venv)$ time python manage.py my_script  # Python 3.4の場合
real    30m34.483s
user    30m34.061s
sys     0m0.373s

これをPyPy3で動かした結果、6分程度で終了した。

(venv-pypy)$ time python manage.py my_script  # PyPy3 2.4.0の場合
real    6m37.536s
user    6m37.238s
sys     0m0.289s

この他、同様に600分程度かかっていたバッチ処理が150分程度に短縮されるなど、CPythonの速度がネックになっていた処理はかなり速くなった。

雑感

  • 速い
  • Pure Pythonサードパーティ製モジュールしか使っていないなら、すんなり動く可能性が高い
  • ループ内で単純な計算、比較処理が多いものは速くなる可能性が高そう
  • バッチ処理などは、設計段階でCモジュールに依存する処理を別のコマンドに分離しておくことで、部分的にPyPyを適用できてよかった

PyPyすごかった。

Mercurialのextensionを作る

Mercurialのextensionは、hgext/モジュール名.pyというレイアウトで作ればいいみたい。
Python 2.7、Mercurial 3.5.2で試した。

$ hg hello
Hello world!

GitHub - tokibito/mercurial-helloworld: helloworld mercurial extension

PyConJP 2015に行ってきた

タイトルの通り。PyConJP 2015に一般参加で行ってきました。
PyCon JP 2015 in Tokyo | October 9th – October 12th
CfPに応募はしていましたが、今年は倍率が高いせいもあってか落選したので一般参加。
聞きたいセッションをいくつか聞いて、それ以外はオープンスペースなどにいました。
オープンスペースは自由に何かやって良いということだったのですが、枠を使う人がほとんどいなくて空いていたので、4枠使わせてもらいました。
数人に対して、いくつかのテーマで話をしたり、ハンズオンをしてました。

  • Djangoコードリーディング (PyCon mini sapporoの話+α)
  • Mercurialで遊ぶ (Mercurialのextensionを作る)
  • プロセスとスレッドの使い方とか (os.forkやthreadingの使い方、違いの説明)
  • cProfileを使おう (cProfileを使ってプロファイルを取る手順、パフォーマンスチューニングの仕方)

待ち時間を過ごす場所として、オープンスペースは良かったので、次回もあるとうれしいなー。
懇親会では、Pythonを使っている他社の方と話せたことがとても良かったです。
クロージングのじゃんけん大会で、パーフェクトPythonをもらいました。持っていなかったので、この機会に読んでみようと思います。
スプリントには参加しませんでしたが、十分満足できました。
スタッフ、スピーカー、スポンサーの皆さんお疲れ様でした&ありがとうございました。