銀月の符号

Python 使い見習いの日記・雑記

setup.py install --home=~

python setup.py install --home=~

export PYTHONPATH=~/lib/python

の合わせ技を思い出した。今度は忘れないようにメモ。

テスト中の Python ライブラリを Python システム外に仮置きする簡単な方法のひとつ。 --home=~ と 8 文字タイプするだけでホームディレクトリ下に置くことができる。そして、このライブラリに Python がアクセスできるようにするには環境変数 PYTHONPATH を指定するのが方法のひとつ。

この手法は root 権限が得られないけれど Python ライブラリをインストールしたい時にも使える。この用途の場合は PYTHONPATH を .bashrc などに書き加えて常態化する。

開発環境などを本来の環境から切り離す、より本格的な方法は virtualenv とか virtual-python とかかな?

スミス数(すみすすう)

すみすさんの すみす.jp ドメイン取得祝いに、スミス数ライブラリを Python で書いたよ。*1

Python 2.6.4 で作成、動作確認。

version 1.0.1 。 Python 2.5.4 でも動作をするように修正した。ただし Python 2.4.4 では動作しないことを確認。

version 1.0.2 。 Python 2.4.4 でも動作をするように修正した。

version 1.0.3 。 Python 2.5 以前で使用する自作 next 関数に多すぎる引数を与えるとエラーメッセージ生成時に TypeError を起こしていたので修正。テストの Python 2.5 以前用対策を修正。

version 1.1 。 各種イテレータが生成する値の初期値を指定できるようにした。 smith_numbers(1000) なら 1000 以上で最も小さいスミス数 1111 から出力する。 smith_numbers() ならいままでどおり最小スミス数 4 から出力する。

スミス数とは

スミス数とは 10 進数表記での数字の和 S(N) と素因子の数字の和 Sp(N) が等しい合成数 N のこと。

例えば 85 は S(85)=8+5=13 であり、 Sp(85)=Sp(5*17)=5+1+7=13 であるためスミス数。 86 は S(86)=8+6=14, Sp(86)=Sp(2*43)=2+4+3=9 なのでスミス数ではない。 7 は S(7)=Sp(7)=7 だけど素数であり、合成数ではないのでスミス数ではない。

>>> from smithnumber import is_smith_number
>>> is_smith_number(85)
True
>>> is_smith_number(86)
False
>>> is_smith_number(7)
False

100 未満のスミス数は 6 個ある。

>>> from itertools import takewhile
>>> from smithnumber import smith_numbers
>>> tuple(takewhile(lambda x: x < 100, smith_numbers()))
(4, 22, 27, 58, 85, 94)

スミス数は無限にある。 smith_numbers イテレータ使用の際には終了条件をつけるのをお忘れなく*2

>>> from smithnumber import smith_numbers
>>> for i in smith_numbers(): # 正常終了せず
...   pass
...

スミス数のバリエーション

http://www.shyamsundergupta.com/smith.htm には、いろんなスミス数が紹介されている。 smithnumber.py にはこのサイトを参考にして作成した、これらのスミス数を順次生成するイテレータ(smith_numbers など)と、ある数がそうなのかどうか判定する関数(is_smith_number など)を詰め込んである。ものによってはなかなか返事が返ってこないものもあるので注意。

Smith Number ([http
//www.research.att.com/~njas/sequences/A006753:title=A006753]):スミス数 (2*2=4, 2*11=22, 3*3*3=27 など)
Smith Semiprime ([http
//www.research.att.com/~njas/sequences/A098837:title=A098837]):2つの素因数からなるスミス数 (2*2=4, 2*11=22, 2*29=58 など)
Palindromic Smith Number ([http
//www.research.att.com/~njas/sequences/A098834:title=A098834]):回文数でもあるスミス数 (121, 1111, 10201, 164461 など)
Reversible Smith Number ([http
//www.research.att.com/~njas/sequences/A104171:title=A104171]):反転させてもスミス数 (319, 913 など)
Fibonacci Smith Number
フィボナッチ数でもあるスミス数 (F31=1346269, F77, F231)
Abundant Smith Number ([http
//www.research.att.com/~njas/sequences/A098835:title=A098835]):過剰数でもあるスミス数 (378, 438, 576 など)
Deficient Smith Number ([http
//www.research.att.com/~njas/sequences/A098836:title=A098836]):不足数でもあるスミス数 (4, 22, 27 など)
Smith Square Number ([http
//www.research.att.com/~njas/sequences/A098839:title=A098839]):平方数でもあるスミス数 (4 など)
Smith Cubic Number ([http
//www.research.att.com/~njas/sequences/A098838:title=A098838]):立法数でもあるスミス数 (27 など)
Smith Trianglar Number ([http
//www.research.att.com/~njas/sequences/A098840:title=A098840]):三角数でもあるスミス数 (378 など)
Repdigit Smith Number ([http
//www.research.att.com/~njas/sequences/A104166:title=A104166]):すべての数字が等しいスミス数 (4, 22, 666, 1111, 6666666など)
k-Consecutive Smith Numbers
k 連続するスミス数の組。 k>=8 の存在は不明。
Smith Brothers ([http
//www.research.att.com/~njas/sequences/A050219:title=A050219]):2 連続スミス数 (728, 729 など)
Smith Triples ([http
//www.research.att.com/~njas/sequences/A105648:title=A105648]):3 連続スミス数 (73615, 73616, 73617 など)
Smith Quads ([http
//www.research.att.com/~njas/sequences/A104649:title=A104649]):4 連続スミス数 (4463535, 4463536, 4463537, 4463538 など)
Smith Quints ([http
//www.research.att.com/~njas/sequences/A105650:title=A105650]):5 連続スミス数 (15966114, 15966115, 15966116, 15966117, 15966118 など)
k-Smith Number
「数字合計のk倍」と「素因数の数字合計」が等しい数。 k=1 で通常のスミス数。
2-Smith Number([http
//www.research.att.com/~njas/sequences/A104390:title=A104390]):(32, 42, 60 など)
3-Smith Number([http
//www.research.att.com/~njas/sequences/A104391:title=A104391]):(402, 510, 700 など)
4-Smith Number([http
//www.research.att.com/~njas/sequences/A103125:title=A103125]):(2401, 5010, 7000 など)
5-Smith Number([http
//www.research.att.com/~njas/sequences/A103126:title=A103126]):(2030, 10203, 12110 など)
(k^-1)-Smith Number
「数字合計の1/k倍」と「素因数の数字合計」が等しい数。 k=1 で通常のスミス数。括弧部分は正しくは k の -1 乗。
(2^-1)-Smith Number([http
//www.research.att.com/~njas/sequences/A050224:title=A050224]):(88, 169, 286 など)
(3^-1)-Smith Number([http
//www.research.att.com/~njas/sequences/A050225:title=A050225]):(6969, 19998, 36399 など)
(4^-1)-Smith Number([http
//www.research.att.com/~njas/sequences/A103123:title=A103123]):(19899699, 36969999, 36999699 など)
(5^-1)-Smith Number([http
//www.research.att.com/~njas/sequences/A103124:title=A103124]):(399996663, 666609999, 669969663 など)

個人的には 2 連続するスミス数の組の名前、「Smith Brothers」の響きが好き。

*1:スミス数自体はなんの役に立つかはわからない(まて、役に立たないものをプレゼントするな)。 n 進数のスミス数を定義して、かつそれに面白い性質が見つかれば化けるのかもしれないけれどそんなことはなさそうだし。でもライブラリ作成過程で素因数分解の方法を学べたり、平方数、立方数、三角数、過剰数、不足数回文数などの数、数列たちと出会えたのはよかった。あとオンライン整数列大辞典というサイトを知ったのも。

*2:itertools.repeat などのようにハードウェアが壊れるか電源供給が止まるかするまで動き続けるということはないはず。かなりの長時間動いた後、巨大な Python 長整数インスタンスが MemoryError を引き起こすことになると予想している。

素因数分解

割り切れるかどうか試してみる、という力業の方法で。

http://tsumuji.cocolog-nifty.com/tsumuji/2009/05/post-af3f.html を参考に作成。素因数分解素数で割ってみることでできる。しかし、素数を求めることそのものに時間がかかるため単に 2 と 3 以上の奇数で割ってみてしまったほうが早い、そして 2, 3 と 6 * n ± 1 (n は 1 以上の整数)で割ったほうがさらに早い、という内容。

factorize.py

# coding: utf-8

u"""素因数分解"""

import itertools

def _ifactorize_p():
    u"""2, 3, および素数の可能性がある奇数 p = 6 * n ± 1"""

    yield 2
    yield 3
    num = 1
    for add in itertools.cycle((4, 2)):
        num += add
        yield num

def ifactorize(num):
    u"""素因数分解
    
    素因数を小さい順に一つずつ返すイテレータ。"""

    num = int(num)
    if num < 1:
        raise ValueError(u'%d is not positive number' % num)
    elif num == 1:
        yield 1
        return

    for fact in _ifactorize_p():
        if num < fact ** 2:
            if num > 1:
                yield num
            break
        q, r = divmod(num, fact)
        while not r:
            num = q
            yield fact
            q, r = divmod(num, fact)

def factorize(num):
    u"""素因数分解
    
    素因数をまとめた tuple を返す
    
    >>> factorize(360)
    (2, 2, 2, 3, 3, 5)
    """

    return tuple(ifactorize(num))

def ifactorize_groupby(num):
    u"""素因数分解
    
    素因数とこれが含まれる数の組を順次返すイテレータ。"""

    for prime, g in itertools.groupby(ifactorize(num)):
        #c = 0
        for c, _ in enumerate(g, 1):
            pass
        yield prime, c

def factorize_groupby(num):
    u"""素因数分解
    
    素因数とこれが含まれる数の組をまとめたタプルを返す。
    
    >>> factorize_groupby(360)
    ((2, 3), (3, 2), (5, 1))
    """

    return tuple(ifactorize_groupby(num))

def main():
    import os
    import sys
    from optparse import OptionParser

    usage = '%prog integer_number [...]\nex: %prog 360'
    parser = OptionParser(usage=usage)
    parser.add_option('-g', '--groupby',
            action='store_true', dest='groupby')
    parser.set_defaults(groupby=False)
    options, args = parser.parse_args()

    if len(args) < 1:
        parser.print_help()
        parser.exit()

    f = factorize_groupby if options.groupby else factorize
    for arg in args:
        try:
            num = int(arg, 10)
            print '%d: %s' % (num, f(num))
        except ValueError, e:
            print e

if __name__ == '__main__':
    main()

実行結果

>factorize.py 360
360: (2, 2, 2, 3, 3, 5)

>factorize.py --groupby 360
360: ((2, 3), (3, 2), (5, 1))

素数列の生成3

素数列の生成2 - 銀月の符号 を微修正。3 以上の奇数の生成方法を itertools.islice(itertools.count(3), 0, None, 2) に変更。うちの実行環境 CPython 2.6.4 on Win32 ではこちらのほうが早かった。 CPython 全般でこちらのほうが早いと予想しているが Jython, IronPython でどうかは見当つかず。あと CPython でも長整数の領域に入ったとき、遅いような気がするが、未確認。

prime.py

# coding: utf-8

u"""素数列の生成 - 遅延評価によるエラトステネスの篩
"""

import itertools

def prime_numbers():
    u"""素数列"""

    yield 2
    odds = itertools.islice(itertools.count(3), 0, None, 2)
    sequences = {4: 2}
    sequences_pop = sequences.pop
    for i in odds:
        d = sequences_pop(i, None)
        if d is None:
            yield i
            i, d = i ** 2, i * 2
        else:
            i += d
            while i in sequences:
                i += d
        sequences[i] = d

def main():
    import sys
    from itertools import takewhile

    max_ = int(sys.argv[1]) if len(sys.argv) > 1 else 30
    for i, prime in enumerate(
            takewhile(lambda x: x < max_, prime_numbers())):
        print i, prime

if __name__ == '__main__':
    main()

実行結果

>prime.py 30
0 2
1 3
2 5
3 7
4 11
5 13
6 17
7 19
8 23
9 29

N 個の組

L[0:2], L[1:3], L[2:4] …のような値を返すイテレータについてのメモ。ほぼ itertools ライブラリドキュメントのレシピ のまま。

L[0:2], L[1:3], L[2:4] … のようなイテレータ

レシピにそのものが載っていたので転載。

from itertools import *

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

L[0:n], L[1:n+1], L[2:n+2] … のようなイテレータ

さきほどの pairwise と consume, repeatfunc というレシピらを参考に作成してみた。

今、疑問に思っている点は値の読み捨てに for 文、 for _ in xrange(n): next(it) ではなく collections.deque を使う理由について。こちらのほうが早いのだろうか? CPython の collections.deque は C 実装なので for 文より早い、とか? 後でベンチとってみようっと。

import itertools
import collections

def setwise(iterable, n=2):
    "s -> (s0,s1, ...), (s1,s2,...), (s2,s3,...), ..."
    its = itertools.tee(iterable, n)
    for i, it in enumerate(its):
        collections.deque(
                itertools.imap(next, itertools.repeat(it, i)),
                maxlen=0)
    return itertools.izip(*its)
使用例、連続する値を見つける

setwise を用いて整数値イテレータブルから、連続する値を見つけてみる。 itertools.ifilter 併用。

>>> from itertools import ifilter, izip, count, starmap
>>> from operator import eq
>>> def is_consecutive_numbers(nums):
...   it = iter(nums)
...   num = next(it)
...   return all(starmap(eq, izip(it, count(num + 1))))
...
>>> data = [1,  4,5,  9,10,11,  16,17,18,19]
>>> for n in ifilter(is_consecutive_numbers, setwise(data, 2)):
...   print n
...
(4, 5)
(9, 10)
(10, 11)
(16, 17)
(17, 18)
(18, 19)
>>> for n in ifilter(is_consecutive_numbers, setwise(data, 3)):
...   print n
...
(9, 10, 11)
(16, 17, 18)
(17, 18, 19)

別解、 itertools.groupby 併用。

>>> from itertools import groupby, imap
>>> from operator import itemgetter
>>> data = [1,  4,5,  9,10,11,  16,17,18,19]
>>> for k, g in groupby(enumerate(data), lambda (i,x):i-x):
...   for n in setwise(imap(itemgetter(1), g), 2):
...     print n
...
(4, 5)
(9, 10)
(10, 11)
(16, 17)
(17, 18)
(18, 19)
>>> for k, g in groupby(enumerate(data), lambda (i,x):i-x):
...   for n in setwise(imap(itemgetter(1), g), 3):
...     print n
...
(9, 10, 11)
(16, 17, 18)
(17, 18, 19)

L[n*0:n*1], L[n*1:n*2], L[n*2:n*3] … のようなイテレータ

これも載っているので、ついでに転載。

from itertools import *

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

gettext モジュールメモ

Python gettext モジュールと戯れる。多言語対応 Python アプリを作るためのモジュール。しかし、オレには日本語以外まともに使える語がない罠。いや、日本語も怪しいけれど。

前提

次のコードを hello.py とする。実行すると「Hello」と挨拶するだけのスクリプト。これをいろんな語の挨拶に代えられるようにしたい、とする。たとえば日本語環境だと「こんにちは」。

# coding: utf-8

def main():
    print u'Hello'

if __name__ == '__main__':
    main()

手順1、マーキング

翻訳対象となる語を _ でマーキングする。次のようになる。

# coding: utf-8

def main():
    print _(u'Hello')

if __name__ == '__main__':
    main()

しかし、この段階で実行すると、 _ ってなに? というエラーが起こる。

>python hello.py
Traceback (most recent call last):
  File "hello.py", line 7, in <module>
    main()
  File "hello.py", line 4, in main
    print _(u'Hello')
NameError: global name '_' is not defined

手順2、 _ をインストール

_ に文字列を置き換えてくれるなんらかの呼び出し可能オブジェクトを対応付ける。ひとつの方法は gettext.translation 関数で得られた「翻訳オブジェクト」の ugettext メソッドを _ とすること。その他の方法については http://pythonjp.sourceforge.jp/dev/library/gettext.html#id3 参照。

# coding: utf-8

import os
import gettext

_ = gettext.translation(
        domain='hello',
        localedir=os.path.join(os.path.dirname(__file__), 'locale'),
        fallback=True).ugettext

def main():
    print _(u'Hello')

if __name__ == '__main__':
    main()

translation 関数の引数の内容はこう。

domain
.mo ファイルの名前
localedir
.mo ファイルの置き場所(省略可能)
fallback
.mo ファイルが見つからなかったとき、翻訳せずに続行するかどうか(デフォルトは False で例外が発生する。 True だとなにもしない NullTranslations オブジェクトが返る。 NullObject パターン。)

この段階で実行すると、まだ翻訳を収めた .mo ファイルが作成されていないため、そのまま Hello と表示される。

>python hello.py
Hello

手順3、.pot ファイルの作成

翻訳対象となる語をまとめた .pot ファイルを作成する。これには pygettext.py スクリプトを用いる。 WindowsPython 2.6.4 には入ってなかったのでソースの Tools\i18n から持ってきた。 pygettext.py hello.py とすると次のような messages.pot ができあがる。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2010-03-19 21:34+東京 (標準時)\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"


#: hello.py:12
msgid "Hello"
msgstr ""

次の .po ファイル作成は翻訳担当者の作業。

手順4、.po ファイルの作成

.po ファイルを作成する。まずは先ほどの messages.pot をコピーして hello.po という名のファイルを作る。そして翻訳作業に入る。たとえばこんな感じ。

# Hello
# Copyright (C) 2010 fgshun
# fgshun <fgshun.lazy-moon.jp>, 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"POT-Creation-Date: 2010-03-19 19:44+JST\n"
"PO-Revision-Date: 2010-03-19 19:57+JST\n"
"Last-Translator: fgshun <fgshun@lazy-moon.jp>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"


#: hello.py:12
msgid "Hello"
msgstr "こんにちは"

.po ファイルのヘッダ部分については http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry 参照。

なお、 .po ファイルを編集するエディタもあるみたい。 poedit など。

手順5、.mo ファイルの作成

hello.po が出来上がったら、これを元に hello.mo ファイルを作ることができる。変換には msgfmt.py スクリプトを用いる。 msgfmt.py も Windows 版にはないので pygettext.py 同様に用意。 msgfmt.py hello.po とすると hello.mo ファイルができる。

次の .mo ファイルの設置はユーザーもしくはインストーラー作成者の作業。

手順6、.mo ファイルの設置

hello.mo ファイルを gettext が読める位置におく。日本語用 .mo は ja\LC_MESSAGES\hello.mo という階層におくべし。

完成

以上で終了。早速日本語で挨拶させてみる。

>python hello.py
こんにちは

md5sum の模倣

Python で md5sum 。車輪の再発明遊び、兼 C コードリーディング。参考にしたのは GNU coreutils-8.4.tar.gz の md5sum.c 。

先日の「md5 のチェックを Python で - 銀月の符号」なんかと違って、ちゃんと各種オプションもある。

得られたもの

  • optparse モジュールのコールバックオプションの使い方
  • (作成過程における)自己満足(完成品に需要がないのは明らか。先日のは即席でもつくれそうなほど簡単なところがメリットになりえたが、これは無理。)

TODO

  • テスト(これはひどい。予定に入っていちゃだめだろ、終わってないと)
  • docstring, コメント(同上)
  • stdin からの入力

以下着手するかどうか未定。

  • リファクタリング(読みやすいコード、テストしやすいコードに。元コードの構造に引きずられて、 Python らしからぬ状態になっているのかも。)
  • 複数のアルゴリズムへの対応(md5sum.c は、これひとつで md5sum, sha1sum, sha256sum, sha224sum, sha512sum, sha384sum のビルドができるようだ。プリプロセッサで振り分けている。 Python 流だとどう料理すべきか?)
  • 多言語対応のための下地作り(たしか _("standard input") の _ は gettext というものだったような。詳しく知らないけれど実行環境のロケールに合った .mo ファイルをもとに一部の表示用テキストを置き換えるものだったっけ。 Python にも gettext モジュールがあることだし、これの使い方の勉強もできる?)

md5sum.py

# coding: utf-8

u"""GNU md5sum の模倣
"""

import sys
import re
import hashlib
import optparse

def split3(line):
    mo = re.match(r'([a-fA-F0-9]{32}) ([ \*])(.*)', line)
    if not mo:
        return None
    digest, is_bin, name = mo.groups()
    if u'\\' in name:
        L = []
        append = L.append
        it = iter(name)
        for c in it:
            if c != u'\\':
                append(c)
            else:
                try:
                    c = next(it)
                except StopIteration:
                    return None
                if c == u'\\':
                    append(u'\\')
                elif c == u'n':
                    append(u'\n')
                else:
                    return None
        name = u''.join(L)
    return digest, is_bin, name

def digest_stream(f, buf_size=2097152):
    ha = hashlib.md5()
    while True:
        data = f.read(buf_size)
        if not data:
            break
        ha.update(data)
    return ha

def digest_file(filename, binary):
    is_stdin = filename == '-'

    if is_stdin:
        # TODO: 標準入力からの入力をなんとかする
        raise Exception(u'Reading from stdin is not supported yet.')
    else:
        with open(filename, 'rb' if binary else 'r') as f:
            ha = digest_stream(f)
        return ha

def digest_check(filename, warn=False):
    is_stdin = filename == '-'

    if is_stdin:
        # TODO: 標準入力からの入力をなんとかする
        raise Exception(u'Reading from stdin is not supported yet.')
    else:
        with open(filename, 'r') as f:
            for line_number, line in enumerate(f, 1):
                if line[0] == '#':
                    continue
                if line[-1] == '\n':
                    line = line[:-1]
                try:
                    digest, is_bin, name = split3(line)
                except TypeError:
                    if warn:
                        print >> sys.stderr, u'%s: %d: improperly formatted MD5 checksum line' % (filename, line_number)
                    continue

                mode = 'rb' if is_bin == '*' else 'r'
                try:
                    with open(name, mode) as g:
                        he = digest_stream(g)
                    result = digest.lower() == he.hexdigest().lower()
                    result_s = u'OK' if result else u'FAILED'
                except IOError, e:
                    print >> sys.stderr, e
                    result = False
                    result_s = u'FAILED open or read'
                yield name, result, result_s

usage = '''%prog [OPTION]... [FILE]...
Print or check MD5(128-bit) checksums.
With no File, or when FILE is -, read standard input.'''

# TODO: version および説明文を考え直すこと
version = '''%prog 0.1
Written by fgshun'''

def status_callback(option, opt, value, parser, *args, **kwargs):
    parser.values.status_only = True
    parser.values.warn = False
    parser.values.quiet = True

def quiet_callback(option, opt, value, parser, *args, **kwargs):
    parser.values.status_only = False
    parser.values.warn = False
    parser.values.quiet = True

def warn_callback(option, opt, value, parser, *args, **kwargs):
    parser.values.status_only = False
    parser.values.warn = True
    parser.values.quiet = False

def main():
    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option('-b', '--binary',
            action='store_true', dest='binary',
            help='read in binary mode')
    parser.add_option('-c', '--check',
            action='store_true', dest='do_check',
            help='read MD5 sums from the FILEs and check them')
    parser.add_option('-t', '--text',
            action='store_false', dest='binary',
            help='read in text mode (default)')
    parser.add_option('--quiet',
            action='callback', callback=quiet_callback,
            help="don't print OK for each successfully verified file")
    parser.add_option('--status',
            action='callback', callback=status_callback,
            help="don't output anything, status code shows success")
    parser.add_option('-w', '--warn',
            action='callback', callback=warn_callback,
            help='warn about improperly formatted checksum lines')
    parser.set_defaults(
            binary=None,
            do_check=False,
            status_only=False,
            warn=False,
            quiet=False)

    options, args = parser.parse_args()

    if options.binary is not None and options.do_check:
        parser.error('the --binary and --text options are meaningless when'
                     'verifying checksums')
    if options.status_only and not options.do_check:
        parser.error('the --status option is meaningful only'
                     'when verifying checksums')
    if options.warn and not options.do_check:
        parser.error('the --warn option is meaningful only'
                     'when verifying checksums')
    if options.quiet and not options.do_check:
        parser.error('the --quiet option is meaningful only'
                     'when verifying checksums')

    if not args:
        args.append('-')

    ok = True

    if options.do_check:
        lines = 0
        computed = 0
        failures = 0
        mismatched = 0
        for arg in args:
            for name, result, result_s in digest_check(arg, options.warn):
                lines += 1
                if result:
                    computed += 1
                else:
                    if result_s == u'FAILED':
                        computed += 1
                        mismatched += 1
                    elif result_s == u'FAILED open or read':
                        failures += 1
                    else:
                        assert True
                    ok = False
                if not (options.status_only or (result and options.quiet)):
                    print u'%s: %s' % (name, result_s)
        if failures and not options.status_only:
            print >> sys.stderr, 'WARNING: %d of %d listed files could not be read' % (failures, lines)
        if mismatched and not options.status_only:
            print >> sys.stderr, 'WARNING: %d of %d computed checksums did Not match' % (mismatched, computed)
    else:
        for arg in args:
            try:
                ha = digest_file(arg, options.binary)
                digest = ha.hexdigest()
                parg = arg.replace(u'\\', u'\\\\')
                parg = parg.replace(u'\n', u'\\n')
                print u'%s %c%s' % (
                        digest, u'*' if options.binary else u' ', parg)
            except IOError, e:
                print >> sys.stderr, e
                ok = False

    sys.exit(0 if ok else 1)

if __name__ == '__main__':
    main()