Python Memo」タグアーカイブ

PythonにおけるOOPのやり方

初めてのPython 第2版

現在第6部「クラスとオブジェクト指向プログラミング」を読んでます。OOPの基本的なことは知っているので、Pythonではどうやるのかという部分を学ぶ。とはいえ演算子のオーバーロード、多重継承など、大事そうだが知らない言葉が色々と出てきている。

例によって、色々試すために簡単な例を書いてみた。

myfirstclass.py

class MyFirstClass:
def __init__(self,title='default'):
self.title = title
def showtitle(self):
print self.title
class MySecondClass:
def showminchar(self):
print min(self.title)
class MyFirstSubClass(MyFirstClass, MySecondClass):
def __add__(self,other):
return MyFirstSubClass(self.title + other)
def __mul__(self,other):
return MyFirstSubClass(self.title * other)
def showtitle(self):
print 'The title is %s' % (self.title)
def getlength(self):
return len(self.title)

__init__というのがいわゆるコンストラクタ。Pythonでは「演算子のオーバーロード」の一種とされているようなので、コンストラクタが実行される際の演算に使われるもの、というのが正しいのかも。__add__というのは+演算子をオーバーロードしている部分で、これによりMyFirstSubClassのインスタンスに+演算を行うことが可能になる。__mul__は*演算子に対応しているもの。

MyFisrtSubClassは、MyFirstClassとMySecondeClassの両方から継承しているサブクラスである。このように多重継承すると、メソッド等は左側から順に採用されるらしい。

実行結果。

>>> Instance1 = MyFirstSubClass('Python')
>>> Instance1.showtitle()
The title is Python
>>> Instance1.getlength()
6
>>> Instance2 = Instance1 + 'GoGo!'
>>> Instance2.showtitle()
The title is PythonGoGo!
>>> Instance3 = Instance2 * 3
>>> Instance3.showtitle()
The title is PythonGoGo!PythonGoGo!PythonGoGo!

多重継承や演算子のオーバーロードがどのように役に立っていくのかはまだ想像できていないのだが(__init__は別として)、クラスのインスタンスをビルトインオブジェクトと同じ形式で演算出来るということは、コードの可読性を高めることには繋がるのかもしれない。

なんにせよ、与えられた道具は上手く使って素晴らしいコードを書きたいものである。

すごく簡単なソケット通信をやってみる

Foundations of Python Network Programming

タイトル通りだけど、ほんとに単純なソケット通信をやってみる。本当は通信をする際には例外処理や終端文字指定、スレッド処理などたくさんやらなければならないことがあるのだが、それは少しずつ実装することにして、まず基本からやろう。

今回はサーバ側とクライアント側を想定して二つのプログラムを作った。同じPC内でポート番号15000を使って通信をする。クライアント側はタイプされた文字列を次々にサーバ側に送り、サーバ側は受け取った文字列を表示するだけである。その内容は以下のようにしてみた。

simpleserver.py

import socket
host = ''
port = 15000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host,port))
s.listen(1)
print 'Waiting for connections...'
clientsock, clientaddr = s.accept()
while 1:
rcvmsg = clientsock.recv(1024)
print 'You received -> %s' % (rcvmsg)
if rcvmsg == '':
break
clientsock.close()

simpleclient.py

import socket
host = 'localhost'
port = 15000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
while 1:
print 'Type message you want to send...'
msg = raw_input()
if msg == '':
s.close()
break
s.sendall(msg)

listen(1)というのは、処理がマルチスレッドではないとして、いくつまでクライアントを待たすことを許すのか、という設定らしい。今回は1つだけ許すということになる。recv(1024)というのはソケットからデータを10KBだけ読み取るということ(今回は問題になりませんが、もしこのプログラムで10KBを超える文字列を送信すると、上手くいかないはず)。

サーバ側を先に実行し、クライアント側を次に実行する。幾つかの文字列を入力した実行結果は以下。

実行結果(クライアント側)

C:\Python24>python.exe simpleclient.py
Type message you want to send...
Hello world!
Type message you want to send...
I love you!
Type message you want to send...
Python is a great language.
Type message you want to send...

実行結果(サーバ側)

C:\Python24>python.exe simpleserver.py
Waiting for connections...
You received -> Hello world!
You received -> I love you!
You received -> Python is a great language.
You received -> 

特に問題なく送れているようです。今後はこれを基に色々と処理を追加していこうかな。

csvファイルをディクショナリで扱ってみる

初めてのPython 第2版

そう言えば、まだディクショナリを使ったことがなかった。いまいち使うシーンが想像できなかったので使ってなかったんだけど、無理やりcsvファイルを扱うことに対して使ってみます。

以下のモジュールreadcsv.pyはファイルemp.csvを読み込み、時給×労働時間をデータごとに計算するという簡単なプログラム。

readcsv.py

def readcsv(filename):
L = [line[:-1] for line in file(filename).readlines()]
return map((lambda s: s.split(',')),L)
csvlist = readcsv('emp.csv')
for line in csvlist[1:]:
empdict = dict(zip(csvlist[0],line))
payment = int(empdict['WAGE']) * int(empdict['HOURS'])
print empdict['NAME'],'->',payment

まずreadcsv関数の中では、csvファイルの各行を行末の1文字(改行記号)を抜いた形で取得し、それぞれの行をカンマで分割してリストにする。返り値は「各行が要素となっているリスト、が要素となっているリスト」になる。ここでは練習のために、リスト内包表記を使ってみた。

実行部では、先ほど作ったリストの1番目の要素(ヘッダー)、と2番目以降の要素を

empdict = dict(zip(csvlist[0],line))

という部分でディクショナリ化する。あとはそれぞれの要素にヘッダーの名前でアクセス出来るようになるので、それを使って給与計算を行い結果を出力している。この方法であれば、CSVファイルのカラムの順番などにも影響されない処理が組めるはず。

emp.csv

NAME,WAGE,HOURS
John,1000,8
Tom,1200,9
Jack,1100,8
Nick,1200,9
Shelly,1200,7
Bill,1000,8
David,1100,9
Ron,1000,8
Kate,1000,9
Mary,1100,9

実行結果。

John -> 8000
Tom -> 10800
Jack -> 8800
Nick -> 10800
Shelly -> 8400
Bill -> 8000
David -> 9900
Ron -> 8000
Kate -> 9000
Mary -> 9900

これと同じ方法で、DBからSQLにて取ってきたデータの処理も出来るはずだ。

モジュールについて勉強中

今は初めてのPython 第2版の第5部「モジュール」を勉強中なので、あまりコードを書く例がない。Pythonはリロードなどによりダイナミックにシステムの修正や改善を行えそうです。モジュールをディレクトリに分けるときは__init__.py作らなければいけないというのは忘れないようにしないと。

そろそろモジュール分けが必要になるくらいの、少し大きめのプログラムを組んでみてもいいかも。

この本もこの後はオブジェクト指向や例外処理の話になってくるので、Python特有と思われる部分の学習は終わりに近づいてきているのかな。そろそろ次のレベルに進みたいな。

あるファイル内の単語数を調べる

初めてのPython 第2版

以前書いた「文章内の単語数を調べる関数」も、文字列のsplit関数とfilter関数の組み合わせで簡単に書き直せた。ちなみにsplit関数とは、文字列を指定した区切り文字で分割し、それぞれをリストの要素として返す関数である。今回は与えられた文字列を空白で分割するようにしている。

def countwords(s):
return len(filter((lambda c: c != ''),s.split(' ')))

これを応用して、今度はあるファイル内にある単語数を調べる関数を作成してみる。引数はファイルパス。

def countwords(s):
return len(filter((lambda c: c != '' and c != '\n'),s.split(' ')))
def countwordsinfile(file):
L = map(countwords,open(file).readlines())
return reduce((lambda x,y: x + y),L)

countwords関数を少しいじったのは、例えば改行だけの行があったときに1文字と認識されてしまう現象が起きたため。open(file).readlines()はファイル内の全ての行をリストの形で返してくれる便利な関数。map関数でそれぞれに行に対してcountwords関数で単語数を調べ、最後にreduce関数で各要素を足していく。

読むファイルは以下。改行コードとEOFは表現のしようがなかったので、とりあえず手書きで加えておきました。

news.txt

The declaration came after the deaths of at least seven Palestinian civilians on a Gaza beach\n
from an apparently errant Israeli artillery shell.\n
\n
Delphi expanded its buyout deal to include all U.A.W. members, signaling a stepped-up effort\n
to avoid a strike.\n
[EOF]

実行結果。

>>> countwordsinfile('news.txt')
41

一つの単語がcondi- tion と二行に亘る場合などに対応できてませんが、とりあえず出来ましたね。

シーザー暗号でも作ってみる

う、時間とネタがないので、今日はシーザー暗号でも作ってみます。暗号化する関数encryptcaesarと、復号する関数decryptcaesarを以下のように定義する。

caesar.py

def encryptcaesar(str,shift=1):
L = list(str)
return ''.join(map(chr,map((lambda s: ord(s) + shift),L)))
def decryptcaesar(str,shift=1):
return encryptcaesar(str,shift*-1)

文字列をリストに直すlist関数と、ある文字のASCIIコードを返すord関数、そしてmap関数を用いると一行で書けてしまいます。

実行結果。

>>> str = encryptcaesar('I love my wife!')
>>> str
'J!mpwf!nz!xjgf"'
>>> decryptcaesar(str)
'I love my wife!'
>>> str = encryptcaesar('Python is great',3)
>>> str
'S|wkrq#lv#juhdw'
>>> decryptcaesar(str,3)
'Python is great'
>>> 

いまどきこんなの暗号とは言えないけど、まあ基本ってことで。

reduce関数でニュートン法

楽しくなってきたんで続けます。

ゲームプログラムめも日記:平方根

会社のプログラム見ていたら、平方根を求める関数があったので、
Pythonで書いてみました。

(中略)

reduceを使えば、2行で書けることが判明しました…!

なんと便利な。早速真似して僕も実践。Newton法を用いてある数の平方根を求める関数mysqrtを作成する。繰り返しの回数が精度を決めるが、とりあえず10回を初期値としておく。

mysqrt.py

from __future__ import division
def mysqrt(x,loop=10):
return reduce((lambda a,x: a - (a**2 - x) / (2 * a)),[x]*loop)

実行結果。

>>> mysqrt(2)
1.4142135623730951
>>> mysqrt(3)
1.7320508075688772
>>> mysqrt(5)
2.2360679774997898
>>> mysqrt(10)
3.1622776601683791
>>> mysqrt(625)
25.0
>>> mysqrt(1024)
32.000000000000803

from __future__ import divisionをきちんと書かないと、割り算の結果が全て切り捨てられてしまうのでご注意を。今気付いたけど、これだと繰り返し9回が初期値か。まあいいや。

何年ぶりかに手書きでもニュートン法を計算してみましたが、プログラムのありがたさがよく分かります。

ちなみに以下のようにmathモジュールをインポートしても、sqrt関数が使えないんだけど何でなんだろう。マニュアル見てもこれで良さそうなのだが。

>>> import math
>>> sqrt(10)
Traceback (most recent call last):
File "<pyshell#56>", line 1, in -toplevel-
sqrt(10)
NameError: name 'sqrt' is not defined

追記:

ちなみに同じ関数をループ使って書くとこんな感じになった。やっぱりスマートじゃないな。

def mysqrt(x):
a = x
cnt = 0
while cnt < 10:
a = a - (a**2 - x) / (2 * a)
cnt += 1
return a

reduce関数で階乗計算をしてみる

関数で遊んでばっかりだけど、これが最後かな。まだリスト内包表記もあるけど。
reduce関数は与えられた式とリストに対して、計算の結果と次の値をまた計算し、その結果と次の値をまた計算し…というのを繰り返してくれる関数。

前に階乗計算を

def factorial(x):
if x == 1:
return 1
else:
return x * factorial(x - 1)

と書いたけど、reduce関数を使えばわずか、

def factorial(x):
return reduce((lambda x,y: x * y),range(1,x+1))

で済んじゃう。速度的なメリットもあるというし、分かりづらいコードにならないのであれば、こっちを使いたくなるのがプログラマ根性ってやつかな。

実行結果は以下。

>>> factorial(1)
1
>>> factorial(2)
2
>>> factorial(3)
6
>>> map(factorial,[4,5,6,10])
[24, 120, 720, 3628800]

filter関数で遊ぶ

Foundations of Python Network Programming

filter関数も非常に便利な関数だ。前にやったIPアドレス取得のプログラムを少し改造した関数を作り、IPアドレスをある個数以上持つホスト名を選び出すフィルタを作ってみよう。

以下のような関数countipaddrを作る。返り値はIPアドレスの個数。

import socket
def countipaddr(hostname):
try:
return len(socket.getaddrinfo(hostname,None))
except socket.gaierror:
return 0

実行結果。

>>> filter((lambda s:countipaddr(s) >= 3),['www.yahoo.co.jp','www.gogle.com','www.hatena.ne.jp','www.yahoe.co.jp','mixi.jp'])
['www.yahoo.co.jp', 'www.gogle.com', 'mixi.jp']
>>> filter((lambda s:countipaddr(s) >= 3),['www.yahoo.com','www.msn.com','www.cnet.com','www.amazon.com','www.goo.co.jp'])
['www.yahoo.com']

AmazonなんかはIPアドレスたくさん持ってないんですね(調べたら一つだった)。逆にmixiはたくさん持ってるな。

filter関数を使えば、前回やったエラトスセネスの篩のアルゴリズムもより簡素の形になるだろう。今度やってみるか。

IPアドレス取得プログラム

Foundations of Python Network Programming
前にJavaでも作ったけど、今度はPythonでIPアドレスの取得プログラムを作ってみる。要はDNSへの問い合わせ。標準入力から受け取ったホスト名称に該当するIPアドレスを返すようにする。

せっかくネットワークの本を買ったんだから、こういうのも作らないと。

ipaddrget.py

import socket
while(1):
name = raw_input()
if name == '':
break
try:
result = socket.getaddrinfo(name,None)
for item in result:
print item[4]
except socket.gaierror:
print "DNS coundn't find %s" % (name)

実行結果。

www.google.com
('66.102.7.147', 0)
('66.102.7.99', 0)
('66.102.7.104', 0)
www.yahoo.com
('66.94.230.48', 0)
('66.94.230.49', 0)
('66.94.230.50', 0)
('66.94.230.75', 0)
('66.94.230.32', 0)
('66.94.230.42', 0)
('66.94.230.44', 0)
('66.94.230.46', 0)
www.hatena.co.jp
('202.181.97.45', 0)
www.yahoe.co.jp
DNS coundn't find www.yahoe.co.jp

getaddrinfoはリストの形で結果を返す関数で、リストの四番目の要素がIPアドレスとポート番号のタプル。今回は実行時にポートを指定していないから0返って来ている模様。ホスト名称が見つからない場合には、socket.gaierrorが返されるから、それに関する処理も入れてみた。

まだ分からないことも多いんで、もう少し突っ込んで学ぶ必要あるな。