2007/06/21

docstringとdoctest…Pythonのドキュメント?

pythonではクラス(メソッド)の説明を次のように書くらしい。
で、次のように書くとそれでテストもできるらしい。
>>> class test:
   """test class

   >>> test = test();
   initted
   >>> test.hello();
   HELLO
   """
   def __init__(self):
      """testclass's init"""
      print "initted";
   def hello(self):
      """print 'HELLO'"""
      print "HELLO";

>>> import doctest;
>>> doctest.testmod();
*** DocTestRunner.merge: '__main__.test.__init__' in both testers; summing outcomes.
*** DocTestRunner.merge: '__main__.test.hello' in both testers; summing outcomes.
*** DocTestRunner.merge: '__main__.doc' in both testers; summing outcomes.
*** DocTestRunner.merge: '__main__.test' in both testers; summing outcomes.
*** DocTestRunner.merge: '__main__' in both testers; summing outcomes.
(0, 2)

※上のはPython Shellでの入出力です。
宣言の直後にコメントを付ける。それがクラスを説明した文章で、docstringと呼ばれている。
docstringにはクラスの説明とクラスの宣言方法、使い方とその出力を記述する。
docstringにクラスの宣言方法、使い方を記述すると、doctestモジュールでテストが行える。
ただし、そのつど出力の変わるようなもののテストはできない。(現在時刻を表示するメソッドなど)

関連項目?

2007/06/15

お題4:入れ子リストの中身を順に表示

お題4:入れ子リストの中身を順に表示 (Python Workshop the Edge 2007)

お題4:入れ子リストの中身を順に表示

整数かリストが入っているような入れ子になったリストを考える。
(例:[1, [2, 3, 4], 5, [[[6], [7, 8], 9], 10])

* 入れ子リストが与えられたときに中身を順に表示するような関数を作れ。
* 与えられたオブジェクトが「整数とリストだけでできている」かどうかをチェックしてTrueかFalseを返す関数を作れ。

というわけで作ってみた。
眠いのでコメント控えめ。
#/usr/bin/env python2.5
# -*- coding: utf-8 -*-

class SearchList:
   def isInt(self, obj):
      if (obj.__class__ == int):
         return True;
      else:
         return False;
   def isList(self, obj):
      if (obj.__class__ == list):
         return True;
      else:
         return False;
   def canRead(self, obj):
      if (self.isInt(obj)):
         return True;
      elif (self.isList(obj)):
         for var in obj:
            if (not self.canRead(var)):
               return False;
         return True;
   def printList(self, obj, message = ""):
      if (self.canRead(obj)):
         for var in obj:
            if (self.isInt(var)):
               message = message + str(var);
            else:
               message = message + str(self.printList(var, message));
      else:
         message = "このオブジェクトは読み込めませんてば。";
      return message;

if ( __name__ == "__main__"):
   #以下、ひたすら動作確認
   list1 = [1, [2, 3, 4], 5, [[[6], [7, 8], 9], 10]];
   list2 = [1, [2, 3, 4], 5, [[[6], [7, "string"], 9], 10]];
   sl = SearchList();
   
   print sl.canRead(list1);
   print sl.printList(list1);

   print sl.canRead(list2);
   print sl.printList(list2);
リストの要素を全てプリントする関数を作って、そこにもしその要素がリストだったらという分岐を加える。
で、リストだったらそのリストの中身を全てプリントする関数(自分自身)を呼び出す。
って感じで関数を作りました。
なぜかクラスにまとめてありますが。
これと課題1を組み合わせればフォルダの中身も全て同期するプログラムが書けるわけですね。

2007/06/14

課題3:シングルトン パート2

リベンジ?
いや、違うけれど…。
#!/usr/bin/env python2.5
# -*- coding: utf-8 -*-

## InstantiatedError
#シングルトン用の例外?
class InstantiatedError(Exception):
   def __init__(self,klass):
      self.message = str(klass) + ' このクラスのインスタンスは一つしか作れません。';

   def __str__(self):
      return repr(self.message);   

class Singleton:
   __instance = None;
   def __init__(self):
      if (self.__class__.__instance != None):
         #インスタンスがあれば例外をはく
         raise InstantiatedError(self.__class__);
      else:#インスタンスが無いなら今作ったものを__instanceに入れる
          self.__class__.__instance = self;

   def getInstance(self):
      if (self.__class__.__instance == None):#インスタンスが作られていなければ作る
         self.__class__.__instance = self;
         return self.__class__.__instance;
      else:#インスタンスが既にあればそれを返す
         return self.__class__.__instance;
      
class Memo(Singleton):
   __text = "";
   def setText(self,text):
      self.__class__.__text = text;#※

   def getText(self):
      return self.__class__.__text;#※

#※ 必ずクラス変数を参照するようにする。

if (__name__ == "__main__"):
   #以下、ひたすら動作確認
   mem1 = Memo();
   try:
      mem2 = Memo();
   except InstantiatedError:
      print "インスタンスが既にありますよ。";
同じインスタンスを作ったら例外をはくようにしてみた。
…なんかこうよくわからない。

お題3:シングルトン

お題3:シングルトン (Python Workshop the Edge 2007)

お題3:シングルトン

Pythonでシングルトンを作れ。

まぁ、シングルトンってなに?っていうところから始めなければならないのよね。
Singleton パターン - Wikipedia
Singletonコンストラクタが一つしか無いことを保証するらしい。 で、作ってみた。
#!/usr/bin/env python2.5
# -*- coding: utf-8 -*-

class Singleton:
   __instance = None;
   def getInstance(self):
      if (self.__class__.__instance == None):
         self.__class__.__instance = self;
         return self.__class__.__instance;
      else:
         return self.__class__.__instance;

class Memo(Singleton):
   __text = "";
   def setText(self, text):
      self.__class__.__text = text;

   def getText(self):
      return self.__class__.__text;

class Memo2(Singleton):
   __text = "";
   def setText(self, text):
      self.__class__.__text = text;

   def getText(self):
      return self.__class__.__text;

if (__name__ == "__main__"):
   #以下、ひたすら動作確認
   mem1 = Memo().getInstance();
   mem2 = Memo().getInstance();
   mem3 = Memo();
   
   print mem1;
   print mem2;
   print mem3;
   
   mem1.setText("momonga");
   
   print mem1.getText();
   print mem2.getText();
   print mem3.getText();
   
   mem3.setText("musasabi");
   
   print mem1.getText();
   print mem2.getText();
   print mem3.getText();
   
   mem2.setText("hattori");
   
   print mem1.getText();
   print mem2.getText();
   print mem3.getText();
   
   tex1 = Memo2().getInstance();
   tex2 = Memo2().getInstance();
   tex3 = Memo2().getInstance();
   
   print tex1;
   print tex2;
   print tex3;
   
   tex1.setText("text2");

   print tex1.getText();
   print tex2.getText();
   print tex3.getText();

   print mem1.getText();
   print mem2.getText();
   print mem3.getText();
…新しいインスタンスの作成自体は防げていない…という出来損ない?
pythonではデータ、メソッドの隠蔽が出来ないらしいのでお手上げ。
newClass = NewClass().getInstance();でインスタンスを取得。
最初に作成されたインスタンスをクラス変数として保存し、すべてのメソッドはそこからテータを取ってくる。
結局これで良いのかどうかもよくわからない。

2007/06/13

お題2:単語数カウント

お題2:単語数カウント (Python Workshop the Edge 2007)

お題2:単語数カウント

英文のテキストファイルを読み込んで単語の出現数を数えるプログラムを作れ。

例えばテキストファイルの中身が「It's fine day, isn't it? Yes, it is!」ならばit'sが1回、fineが1回、dayが1回、isn'tが1回、itが2回、yesが1回、isが1回となるように数えてよい。
(isn'tにはisが含まれているな、とか、It'sの'sはisの省略形だな、などと判断するのはとてもむずかしいので)

余力があれば出現頻度の多い順に出力するプログラムも書け。

で、作ってみた。
#!/usr/bin/env python2.5
# -*- coding: utf-8 -*-

import re;

## wordCount
# 文字列を渡すと、英単語に分解して出現頻度を求める
# splitPattern = 区切り文字のパターン指定(それっぽいものを適当に並べただけ)
# text = 指定した区切り文字で受け取った文字列を分割し、リストを作成する
# words = {単語:出現数}の辞書
def wordCount(src):
 splitPattern = re.compile(' |\t|\n|\r|\?|\!|\(|\)|\"|\.|\,');
 
 text = splitPattern.split(src);

 words = {};

 #単語の出現回数を数える
 for word in text:
  #空の単語は無視する
  if (word != ''):
   if (word in words):#今までに出たことがあるなら出現回数に+1する
    words[word] += 1;
   else:#初めての単語なら新規作成
    words[word] = 1;
 
 return words;

## sort
# wordCountで作った辞書をソートする。
# wordCount専用だからいっそclassにまとめた方が良いかも…。
def sort(dict):
 #出現回数、アルファベットの順にプリントしていく
 for word, count in sorted(words.iteritems(), lambda x,y:(y[1] == x[1] and cmp(x[0].lower(), y[0].lower())) or y[1]-x[1]):
  print word + ": " + str(count) + ",",

if (__name__ == "__main__"):
 from sys import argv,exit;
 if (len(argv) != 2):
  print 'ファイル指定は一つまででお願いします。';
  exit();
 
 text = open(argv[1]).read();
 words = wordCount(text);
 print words;
 sort(words);
うへぇ。
ソートの仕方が全くわかりませんでした。
lambda式とsortedが組合わさると、両方わからない私にとってはどんな魔法だって感じですよ。
sortedの第一引数で指定したリストから2つ取って来て第二引数に渡す。
戻り値が+だとそのままで-だと入れ替える?(頭が働かない…)
とりあえず呪文は暗記しておこうかね?

lambdaとかについて

これ(↓)を、
def sort(dict):  #出現回数、アルファベットの順にプリントしていく
 for word, count in sorted(words.iteritems(), lambda x,y:(y[1] == x[1] and cmp(x[0].lower(), y[0].lower())) or y[1]-x[1]):
  print word + ": " + str(count) + ",",
lambda式を使わない、かつ、もっと細かくて順を踏んで書くとこんな(↓)感じになるらしい。
def sort(dict):
 sortedWords = sorted(words.iteritems(), cmp_custom);
 for word, count in sortedWords:
  print word + ": " + str(count) + ",",

def cmp_custom(x,y):
 if (y[1] == x[1]):
  return cmp(x[0].lower(), y[0].lower());
 else:
  return y[1]-x[1];
ソートで使う比較用の関数を新しく作り、sortedの第二引数でその関数を指定してあげる。
それで、ソート済みのリスト((ワード,出現回数)のタプルが入ったリスト。print sortedWordsで見てみると良くわかる?)が取得できる。
取得したリストをfor文でぶん回して出力すればOKらしい。
…それにしても、変数の命名にかなりの問題がある気がする。

Pythonの勉強のために

Python Workshop the Edge 2007のお題をやって見ようと思う。
お題1:ファイルの同期 (Python Workshop the Edge 2007)

お題1:ファイルの同期

二つのディレクトリ下のタイムスタンプを比較して、同期を取るプログラムを作りたい。

二つのディレクトリ(フォルダ)を指定すると、そのディレクトリの下の全てのファイルについて「片方にしかないファイルはもう片方へコピーし、両方にあるけどもタイムスタンプ(更新時刻)の異なるファイルは新しい方で古い方を上書きする」という処理を行うプログラムを作りなさい。

で、作ってみた。
#! /usr/bin/env python2.5
# -*- coding: utf-8 -*-

from os import listdir, stat, sep;
from os.path import getmtime;
from shutil import copy2;

## sync
# 二つのディレクトリを比較し、同期をとる。
# filesX = ディレクトリの中身の集合
# to1Files = ディレクトリ1へコピーするファイル集合(2にしかないファイル集合)
# to2Files = ディレクトリ2へコピーするファイル集合(1にしかないファイル集合)
# bothFiles = 両方のディレクトリにあるファイル集合(後でタイムスタンプを比較するファイル集合)
def sync(dir1, dir2):
   #ディレクトリ1,2それぞれのファイル集合を作成
   files1 = set(listdir(dir1));
   files2 = set(listdir(dir2));

   #どのファイルをどのsetに入れるか振り分ける。
   to1Files = files2.difference(files1);
   to2Files = files1.difference(files2);
   bothFiles = files1.intersection(files2);

   #bothFilesに含まれるファイルのタイムスタンプを比較し振り分ける
   for file in bothFiles:
      #比較するファイルのパス作成
      file1 = dir1 + sep + file;
      file2 = dir2 + sep + file;

      #タイムスタンプの比較と振り分け。
      if (getmtime(file1) > getmtime(file2)):
         to2Files.add(file);
      elif (getmtime(file2) > getmtime(file1)):
         to1Files.add(file);
   
   # to1Files,to2Filesのファイルをコピーすべきディレクトリへコピーする
   for file in to1Files:
      copy2(dir2 + sep + file, dir1 + sep + file);
   for file in to2Files:
      copy2(dir1 + sep + file, dir2 + sep + file);
   
if (__name__ == "__main__"):
   from sys import argv, exit;
   if (len(argv) != 3):
      print "ディレクトリを2つ指定して下さい。";
      exit();
   
   sync(argv[1],argv[2]);
最初は無駄にクラス作っていたけれど削りに削って最終的にこんな感じになりました。
ほかのお題もやってみようかしら?