2017/04/26

Python Tips: GetText (.po) ファイルの要素を抽出したい

Python で、 GetText (.po) ファイルの要素を抽出する方法をご紹介します。

「 GetText って何?」という方は Wikipedia を参考になさってみてください。

- gettext - Wikipedia

pip パッケージのひとつに polib というものがあり、こちらを使うと GetText (.po) ファイル(以下 .po ファイル)を Python でシンプル・かんたんに扱うことができます。


インストール


インストールにはおなじみ pip コマンドを使用しましょう。

pip install polib


使い方


.po ファイルの読み込みには pofile() 関数を使います。

import polib

po = polib.pofile('path/to/catalog.po')

作成日や作成者を含むメタデータは metadata プロパティに格納されています。

print(po.metadata)

.po ファイルに含まれる各翻訳テキストは POFile オブジェクトをイテレータとして使用すると取得することができます。

for entry in po:
    print('{}: {}'.format(entry.msgid, entry.msgstr))

有効な翻訳文を持つ翻訳テキストのみに限定して取得したい場合は translated_entries() メソッドが便利です。

for entry in po.translated_entries():
    print('{}: {}'.format(entry.msgid, entry.msgstr))

以上です。

ここでご紹介したのは .po ファイルの要素を抽出する方法だけですが、 polib では他にも「要素の追加」「 .po ファイルの新規作成」「 .po ファイルの更新」などひととおりの処理がサポートされています。興味のある方は公式の Quick start guide をご覧になってみてください。

Quick start guide — polib documentation

私の場合は「巨大な .po ファイルのうち一部の要素を切り出して小さな .po ファイルを作りたい」という要望があり、 .po ファイルからの要素抽出スクリプトを作成するのに使用しました。興味のある方は次の gist の方も参考にしてみてください。



参考

- polib : Python Package Index
- Welcome to polib’s documentation! — polib documentation

2017/04/02

Python Tips:特定のサイズ以上のファイルを検索したい

バタバタしており久しぶりの投稿になってしまいました。

今回は Python で指定されたサイズ以上のファイルを検索する方法をご紹介します。

これを実現するアプローチとしてはいくつかの方法が考えられるかと思いますが、今回は標準ライブラリの pathlib を使った方法をご紹介してみたいと思います。

from pathlib import Path

pathlib の Path クラスを使えば OOP スタイルで OS のファイルシステムを操作することができます。

Path クラスの数あるメソッドのうち次の 4 つほどをおさえておけば、ファイルを探す処理の実装には十分でしょう。

- iterdir() ディレクトリの中にあるファイル / ディレクトリを全件返す
- is_dir() ディレクトリかどうかをチェックする
- is_file() ファイルかどうかをチェックする
- stat() ファイルサイズを含むファイルのメタ情報を返す

サンプルコードを書いてみます。

# coding: utf-8
from pathlib import Path

def search_files(path, size_min_in_byte):
    """指定されたパスの下にある指定されたサイズ以上のファイル名を一覧表示する
    """
    size_min_in_mb = size_min_in_byte << 20

    p = Path(path)

    # 指定されたパス以下のファイルを再帰的にチェックする
    # 指定されたサイズ以上のファイルは「 10MB  ファイル名」といった感じに表示する
    for file in p.iterdir():
        if file.is_dir():
            search_files(file, size_min_in_byte)
        elif file.is_file():
            size = file.stat().st_size
            if size >= size_min_in_mb:
                # resolve() を使って絶対パスを表示する
                print('{:.1f}MB\t{}'.format(size >> 20, file.resolve()))


if __name__ == '__main__':
    # hayato のデスクトップ以下にあるサイズが 1MB 以上のファイルを表示する
    path = '/Users/hayato/Desktop'
    size_in_mb = 2
    search_files(path, size_in_mb)


ファイル名を script_name.py として保存しターミナルで実行してみましょう。私の環境では例えば次のような出力が出ます。

$ python script_name.py
3.5MB /Users/hayato/Desktop/山月記.md
2.2MB /Users/hayato/Desktop/弟子.md
2.7MB /Users/hayato/Desktop/李陵.md

これはちょうど、 find コマンドでファイルサイズを指定した場合と同じような結果になります。

find /Users/hayato/Desktop -type f -size +2M

ここでは単純にサイズとファイル名を標準出力に返していますが、対象のファイルそれぞれに対して特定の処理を行いたい場合などは print 文の前後を必要な処理に差し替えるとよいものと思います。

以上です。

例えば、コマンドライン引数をかんたんに扱うための標準ライブラリ argparse といっしょに使うともう少し汎用性の高いスクリプトを作ることができます。興味のある方は次のスニペットもよろしければ参考にしてみてください。


2016/09/27

Python Tips:デコレータに引数を渡したい

Python のデコレータに引数を渡す方法について見てみます。

具体的には「引数を取ることができるデコレータの作り方」を見ていきます。

まずはかんたんにおさらいから。 Python では `@デコレータ` という形でデコレータを使うことができます。

# デコレータ関数 add_print() の定義
def add_print(func):
    def wrapper(*args):
        print("{} is invoked.".format(func.__name__))
        func(*args)

    # ラップした関数そのものを返す
    return wrapper

# デコレータ関数の利用
@add_print
def myfunc():
    print("Hello!")

# 動作確認
myfunc()
# =>
# myfunc is invoked.
# Hello!

myfunc() を add_print() でデコレートできていることが確認できます。

デコレータ機能をシンプルに使うだけの場合はこれでよいのですが、デコレータを使ってちょっと凝ったことをやろうとするとデコレータにパラメータを渡したくなってきます。今回はその方法を見ていきます。

作り方の前に使い方を先に見てみましょう。引数を受け取れるデコレータは次のように使います。

@デコレータ関数を生成する関数(引数)
def デコレート対象の関数(引数):
    関数の中身

引数を取らないデコレータの場合と見比べてみましょう。引数を取らないデコレータは次のように書きます。

@デコレータ関数
def デコレート対象の関数(引数):
    関数の中身

両者のちがいは、「 @ 」の後ろが「デコレータ関数を生成する関数(引数)」となっているか「デコレータ関数」となっているかです。

「デコレータ関数を生成する関数」と書いているのはまちがいではありません。引数を受け取れるデコレータというのは、実は「引数を受け取ってデコレータ関数を生成する関数」にほかなりません。

・・・概念的な説明だけだと何を言ってるのかわけわからないので具体例を見てみましょう。

sample.py:

# coding: utf-8

import os


def main():
    """main 関数
    """
    print("CSV generator.")
    names = None
    while not names:
        print("Enter file names with comma delimiter: ", end="")
        names = input().strip().split(",")

    create_files(*[name + ".csv" for name in names])


def add_print(pattern):
    """ファイルの作成前後に表示を行うデコレータ
    """
    def _add_print(func):
        def wrapper(*args):
            print("Creating {} {} ... ".format(pattern, ",".join(args)), end="")
            func(*args)
            print("Done!")
        return wrapper

    return _add_print


@add_print("files")
def create_files(*paths):
    """空のファイルを作成する
    """
    for p in paths:
        with open(p, "a"):
            os.utime(p, None)


if __name__ == "__main__":
    main()

動作イメージ:

python sample.py
CSV generator.
Enter file names with comma delimiter: a,b
Creating files a.csv,b.csv ... Done!

少し長いですが、ポイントになるのはごく一部です。

デコレータの使用場所は以下の部分です。

@add_print("files")
def create_files(*paths):
    """空のファイルを作成する
    """
    for p in paths:
        with open(p, "a"):
            os.utime(p, None)

この add_print() というのが上述の「引数を受け取ってデコレータ関数を生成する関数」になります。これはデコレータ関数ではなく、引数 files を受け取ってデコレータ関数 _add_print() を生成しています。そして、戻り値である _add_print() が create_files() を実際にデコレートし、 wrapper() という関数に置き換えてデコレート処理が完了するという流れです。

ということで、「引数を受け取れるデコレート関数」というものは実は見た目上のものであって、実際には「引数を受け取ってデコレート関数を生成する関数」を作成する必要があります。

元々デコレータ関数というもの自体が「関数を返す関数」なので、「デコレータを返す関数」となると自然「関数を返す関数を返す関数」という入れ子構造となります。見た目はとてもややこしいのですが、自分で実際に書いてみると構造がよくわかってすっきり見通せるようになるので、興味があってまだ書いたことのない方はよろしければぜひ自分の手でひとつ書いてみてください。

参考:

- Pythonのデコレータの使い方