Luhnアルゴリズムでクレジットカードの番号をチェック

クレジットカード番号のチェックサムには Luhnアルゴリズムというのが使われているのを知った。

cf. Luhnアルゴリズムでクレジットカードの番号をチェック – brainstorm
cf. Luhnアルゴリズム – Wikipedia

Rubyでやってみた。

# coding: utf-8
#
# Check number with Luhn algorithm

def check_number(digits)
  even = false
  sum = digits.reverse.split(//).inject(0) do |s, d|
    d = d.to_i
    if even
      d = d * 2
      s -= 9 if d > 9
    end
    even = !even
    s += d
  end
  sum % 10 == 0
end


NUMBERS = [
  '5555555555554444',
  '5105105105105100',
  '4111111111111111',
  '4012888888881881',
  '3530111333300000',
  '3566002020360505',
  '30569309025904',
  '38520000023237',
  '378282246310005',
  '371449635398431',
  '378734493671000',
  '6011111111111117',
  '6011000990139424'
]

NUMBERS.each do |digits|
  puts check_number(digits)
end

カード番号の例は↓ここで紹介されているもの。
cf. ECサイトの動作テストに使える、クレジットカードのテスト番号一覧 – Webクリエイターボックス

実行結果:

^o^ > check_card_numbers.rb
true
true
true
true
true
true
true
true
true
true
true
true
true

easy_install

easy_install は Python のパッケージインストーラ、というかコマンドだ。Setuptools や Distribute に含まれている。で、ここがちょっと混乱するところなんだけど、Setuptools を使えばいいのか、それとも Distrubute を使うほうがいいのか、ってこと。Web の情報を見てみると、Setuptools は開発がとまっていて、そこから fork したのが Distribute らしい。

cf. エキPy読書会(第2期) 第1回に行ってきました – 今川館

というわけで、Distribute をインストールしてみた。

Distribute をインストールするには、次のURLからスクリプトをダウンロードして実行すればいい。

http://python-distribute.org/distribute_setup.py

^o^ > wget http://python-distribute.org/distribute_setup.py
^o^ > python distribute_setup.py

なんかいろいろとメッセージが出て、インストールが完了したらしい。
Pythonをインストールしたディレクトリの下にScripts\easy_install.exe というファイルができているので、ここにパスを通せばOK。
これで easy_install コマンドが使えるようになったはず。

^o^ > easy_install

ところが、いざ実行すると「ユーザーアカウント制御」というダイアログがでてきて、「はい」をクリックしても「いいえ」をクリックしてもそのまま終了してしまう。どうやら、Windows 7 のユーザー権限にひっかかっているらしい。どうすりゃいいんだ。

[追記]
どうもファイル名にinstallと入っているのが原因らしい。
cf. http://phpy.readthedocs.org/en/latest/install.html

次のようにすれば回避できるようだ。

^o^ > python -m easy_install
error: No urls, filenames, or requirements specified (see --help)

^o^ > python -m easy_install --help

Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message
  --no-user-cfg   ignore pydistutils.cfg in your home directory

Options for 'easy_install' command:
  --prefix                       installation prefix
  --zip-ok (-z)                  install package as a zipfile
  --multi-version (-m)           make apps have to require() a version
  --upgrade (-U)                 force upgrade (searches PyPI for latest
                                 versions)
  --install-dir (-d)             install package to DIR
  --script-dir (-s)              install scripts to DIR
  --exclude-scripts (-x)         Don't install scripts
  --always-copy (-a)             Copy all needed packages to install dir
  --index-url (-i)               base URL of Python Package Index
  --find-links (-f)              additional URL(s) to search for packages
  --delete-conflicting (-D)      no longer needed; don't use this
  --ignore-conflicts-at-my-risk  no longer needed; don't use this
  --build-directory (-b)         download/extract/build in DIR; keep the
                                 results
  --optimize (-O)                also compile with optimization: -O1 for
                                 "python -O", -O2 for "python -OO", and -O0 to
                                 disable [default: -O0]
  --record                       filename in which to record list of installed
                                 files
  --always-unzip (-Z)            don't install as a zipfile, no matter what
  --site-dirs (-S)               list of directories where .pth files work
  --editable (-e)                Install specified packages in editable form
  --no-deps (-N)                 don't install dependencies
  --allow-hosts (-H)             pattern(s) that hostnames must match
  --local-snapshots-ok (-l)      allow building eggs from local checkouts
  --version                      print version information and exit
  --no-find-links                Don't load find-links defined in packages
                                 being installed
  --user                         install in user site-package
                                 'C:\Users\takatoh\AppData\Roaming\Python\Python2
                                 7\site-packages'

usage: easy_install.py [options] requirement_or_url ...
   or: easy_install.py --help

日本語とファイル入出力

日本語ファイルの入力

前回のエントリに書いたとおり、Pythonの内部ではユニコード文字列を使うのがいいようだ。日本語のファイルを読み書きする場合、読み込んだ文字列をユニコードにデコードし、何らかの処理したあと、出力するときに出力先にあわせてエンコードする、という流れになる。
次の例は、シフトJISで書かれた名前のファイルを読み込んで、挨拶をするプログラムだ。

# coding: utf-8

import sys

input_file = open(sys.argv[1], "r")

for name in input_file:
    uname = name.decode("sjis").rstrip("\r\n")
    msg = u"こんにちは、" + uname
    print msg.encode("sjis")

読み込んだ文字列を10行目でデコードしてユニコード文字列にし、12行目で出力する際にシフトJISにエンコードしている。

実行:

^o^ > type nihongo_sjis.txt
アンディ
ビル
チャーリー

^o^ > python konnichiwa4.py nihongo_sjis.txt
こんにちは、アンディ
こんにちは、ビル
こんにちは、チャーリー

codecsモジュールのopen関数を使うと、ファイルから読み込んだ文字列をデコードする手間が省ける。codecs.open関数には、ファイルのエンコーディングを指定することができる。

# coding: utf-8

import sys
import codecs

input_file = codecs.open(sys.argv[1], "r", "sjis")

for name in input_file:
    name = name.rstrip("\r\n")
    msg = u"こんにちは、" + name
    print msg.encode("sjis")

ファイルをオープンするときにエンコーディングを指定していて、読み込んだ文字列ばデコードしなくてもユニコード文字列になっている。

^o^ > python konnichiwa4a.py nihongo_sjis.txt
こんにちは、アンディ
こんにちは、ビル
こんにちは、チャーリー

日本語のファイル出力

codecs.open関数はファイルに日本語を書き込むときにも使える。

# coding: utf-8

import sys
import codecs

output_file = codecs.open("nihongo_out_sjis.txt", "w", "sjis")

output_file.write(u"こんにちは、世界")
^o^ > python konnichiwa5.py

^o^ > type nihongo_out_sjis.txt
こんにちは、世界

Windows のコマンドプロンプトでちゃんと表示されるってことはファイルの中身がシフトJISになってるってことだ。

日本語のファイル名

Pythonでは、ファイル名を扱うほとんどの関数がユニコード文字列に対応している。引数としてユニコード文字列を渡してやれば、あとはプラットフォームにあわせてうまくやってくれる。

# coding: utf-8

import sys
import codecs

input_file_name = sys.argv[1].decode("sjis")
input_file = codecs.open(input_file_name, "r", "sjis")

for wday in input_file:
    wday = wday.rstrip("\r\n")
    print wday.encode("sjis")

実行結果:

^o^ > type 日本語.txt
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日

^o^ > python nihongo.py 日本語.txt
月曜日
火曜日
水曜日
木曜日
金曜日
土曜日
日曜日

ユニコード文字列と8ビット文字列

ユニコード文字列と8ビット文字列

Python の文字列にはユニコード文字列と8ビット文字列がある。8ビット文字列というのが正式な言い方かどうかはわからないけど「みんなのPython」の中ではそう呼んでいる。
で、どう違うかというと、言ってみればユニコード文字列はPythonの内部表現で、8ビット文字列はその他のエンコードを持った文字列といってよさそう。

ユニコード文字列のリテラルには、頭に u をつける。

>>> s = u'abc'
>>> type(s)
<type 'unicode'>

上にあるように、ユニコード文字列の型は unicode になる。これに対して普通の文字列、つまり8ビット文字列の型は str だ。

>>> s2 = 'abc'
>>> type(s2)
<type 'str'>

スクリプトエンコーディング

ASCII文字だけを使うときは気にすることは無いけど、日本語を使うときにはスクリプトのエンコーディングにも気をつけなきゃいけない。といっても、Pythonの場合は UTF-8 にしておくのはほとんどデフォルトのようだ。
スクリプトのエンコーディングは、1行目か2行目に次のように書く。

# coding: utf-8

もちろん、スクリプトファイルをUTF-8で保存するのを忘れずに。

日本語の出力

Windowsで日本語を出力するときには、ユニコード文字列をShift-JISに変換してやる。
エンコードの変換には、文字列の encode 関数が使える。

# coding: utf-8

s = u'こんにちは'

print s.encode('sjis')

実行結果:

^o^ > python konnichiwa.py
こんにちは

実はわざわざsjisにエンコードしなくてもちゃんと出力される。

# coding: utf-8

s = u'こんにちは'

print s

実行結果:

^o^ > python konnichiwa2.py
こんにちは

どうも暗黙のうちにコンソールに合わせてエンコードしてくれているみたいだ。だけど、これだとファイルにリダイレクトするとうまくいかない。

^o^ > python konnichiwa2.py > konnichiwa.txt
Traceback (most recent call last):
  File "konnichiwa2.py", line 5, in 
    print s
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordin
al not in range(128)

‘ascii’ codec がエンコードできないといっている。標準出力のときにはうまくいくのにリダイレクトすると何でだめなのか、謎。

日本語を入力

コマンドラインから日本語を入力することを考える。
スクリプトの外部から来た文字列はすべて8ビット文字列だ。だから、ユニコード文字列に変換してやる必要がある。変換には文字列の decode 関数が使える。

# coding: utf-8

import sys

name = sys.argv[1].decode('sjis')

s = u'こんにちは' + name

print s.encode('sjis')

実行結果:

^o^ > python konnichiwa3.py アンディ
こんにちはアンディ

Pythonで使えるエンコード名

最後に書いておこう。

エンコードPythonのエンコード名
シフトJISshift-jis shift_jis sjis
ISO-2022-JPiso-2022-jp
EUC-JPeuc-jp
UTF-8utf-8

Python Shellのプロンプトの表示を変更する:Windows編

Python Shellのプロンプトの表示を変更する。sys.ps1,sys.ps2を変更する。 ― oneshotlife_tomの日記を読んで、Windowsでもやってみた。
Python Shell のプロンプトってのは↓これのことね。

>>>

上の記事ではWindowsでのやり方はわからなかったように書いてあるけど、結果からいえば同じやり方でできた。つまり、sys.ps1 や sys.ps2 にほかの文字列を代入してやればいい。ちなみに Python 2.7.3。

>>> import sys
>>> sys.ps1 = '$'
$'spam'
'spam'
$sys.ps2 = ']]]'
$for i in range(3):
]]]    print 'spam'
]]]
spam
spam
spam
$