2010年10月30日土曜日

課題2:解答編

だいぶ間が開いてしまいましたが、解答編を書いときます。

まずは、ソースコード全体を。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import gtk
import glib

class SlideShow:
    
    def __init__(self):
        
        #GTK.Builderを構築
        wTree = gtk.Builder()
        #Gladeファイルの読み込み
        wTree.add_from_file("SlideShow.ui")
        #MainWindowオブジェクトを取得する
        self.mainWindow = wTree.get_object("MainWindow")
        #シグナルとシグナルハンドラをdic形式で作成する
        dic = {"on_MainWindow_destroy":self.on_MainWindow_destroy}
        #シグナルハンドラとシグナルを結びつける
        wTree.connect_signals(dic)
        #スライドショー用のGTKImageオブジェクトを取得する
        self.imgSlideShow = wTree.get_object("imgSlideShow")
        #画像のパスを取得
        self.imagePath = []
        #os.walk関数により、再帰的にフォルダを降りていき画像を探す
        for base, path, imPath in os.walk("/usr/share/backgrounds"):
            for img in imPath:
                limg = img.lower()
                #jpg/png/jpegファイルだけを対象にする
                if limg.find("jpg") > 0 or limg.find("png") > 0 or limg.find("jpeg") > 0: 
                    self.imagePath.append(base+"/"+img)
        #初期画面をロード
        #初期画面を表示するためのカウンタを初期化
        self.count = 0 
        #gtk.Imageに画像ファイルを表示する関数をコールする
        self._loadImage()
        #ウィンドウを表示する
        self.mainWindow.show_all()
        self.timeout = glib.timeout_add_seconds(2,self._timeouf,self)
        
    
    def on_MainWindow_destroy(self,widget):
        #GTKのメッセージループを終了する
        gtk.main_quit()
    
    def _timeouf(self,event):
        #gtk.Imageに画像ファイルを表示する関数をコールする
        self._loadImage()
        return True
        
    def _loadImage(self):
        #現在のカウンタを使用して画像ファイル名を取得。
        wallpaper = self.imagePath[self.count]
        #現在のウィンドウサイズを取得(x,y)のタプルで返る
        size = self.mainWindow.get_size()
        #新規にPixBufを作成し、指定されたファイルを読み込む
        pixbuf = gtk.gdk.pixbuf_new_from_file(wallpaper)
        #読み込んだPixBufの縦と横のサイズを取得
        x = float(pixbuf.get_width())
        y = float(pixbuf.get_height())
        #アスペクト比を算出
        aspect = y / x
        #pixbuf.scale_simpleメソッドを使用し、ウィンドウにマップされたImage用のpixbufを作成する
        #その際、アスペクト比を合わせる。基準は横幅
        pixbuf2 = pixbuf.scale_simple(size[0], int(size[0]*aspect),gtk.gdk.INTERP_BILINEAR )
        #使い終わったPixBufを削除    
        del pixbuf
        #ウィンドウにマップされたgtk.Imageに新規にリサイズしたPixBufをアサイン
        self.imgSlideShow.set_from_pixbuf(pixbuf2)
        #アスペクト比に合わせてウィンドウサイズをリサイズ
        self.mainWindow.resize(size[0],int(size[0]*aspect))
        #画像リスト用のカウンタをカウントアップ
        if len(self.imagePath) > self.count+1:
            self.count += 1
        else:
            self.count = 0

if __name__ == "__main__":
    #イメージ表示ウィンドウのコンストラクタを実行する(オブジェクトを作成する)
    SlideShow()
    #GTKのメッセージループを実行する
    gtk.main()


これに、課題2の時に母体コードとして提供したSlideShow.uiファイルを組み合わせれば、2秒置きに画像が表示されるスライドショーが完成します。

今回、ポイントとなるのは、以下の関数というかメソッドなわけですが。
def _loadImage(self):
        #現在のカウンタを使用して画像ファイル名を取得。
        wallpaper = self.imagePath[self.count]
        #現在のウィンドウサイズを取得(x,y)のタプルで返る
        size = self.mainWindow.get_size()
        #新規にPixBufを作成し、指定されたファイルを読み込む
        pixbuf = gtk.gdk.pixbuf_new_from_file(wallpaper)
        #読み込んだPixBufの縦と横のサイズを取得
        x = float(pixbuf.get_width())
        y = float(pixbuf.get_height())
        #アスペクト比を算出
        aspect = y / x
        #pixbuf.scale_simpleメソッドを使用し、ウィンドウにマップされたImage用のpixbufを作成する
        #その際、アスペクト比を合わせる。基準は横幅
        pixbuf2 = pixbuf.scale_simple(size[0], int(size[0]*aspect),gtk.gdk.INTERP_BILINEAR )
        #使い終わったPixBufを削除    
        del pixbuf
        #ウィンドウにマップされたgtk.Imageに新規にリサイズしたPixBufをアサイン
        self.imgSlideShow.set_from_pixbuf(pixbuf2)
        #アスペクト比に合わせてウィンドウサイズをリサイズ
        self.mainWindow.resize(size[0],int(size[0]*aspect))
        #画像リスト用のカウンタをカウントアップ
        if len(self.imagePath) > self.count+1:
            self.count += 1
        else:
            self.count = 0
中身に関しては、ソースコードの一行ごとにコメント入れたので、そちらを参照してもらうとして。

これが、2箇所でコールされている、ということです。
__init__と_timeoufの2箇所ですね。
もちろん、もっとシンプルな処理の場合、2箇所に同じ処理を書いてもいいのですが、基本的に、gtk.Imageに指定された画像ファイルを表示する、という全く同じ処理を行っているわけです。

さすがに、ここまで長いと理解できると思いますが、同じ処理を何度も書くのはムダなんですね。
どのようにムダかというと、以下の点でムダ。

同じことを2回書かなくてはならない(コピペでいいじゃんという説はある)
もしも修正した場合、同じ箇所を2回直さないとならない(コピペでいいじゃんという説はある)
同じコードが2度も現れると読みにくい

いずれの場合も、同じ手間を2度行わなくてはならないのがムダ、ということになります。
で、まあ2箇所くらいなら、別にコピペでもいいんですが、これが20箇所とかになると、さすがにコピペも厳しいし、読みづらくなるし、コピペ漏れも出てきます。
そのため、同じ処理はまとめてしまって、関数、あるいはローカルメソッドして定義してしまうほうが効率がよいわけです。

ローカルメソッドとするか、関数とするかは、スコープの問題なので、各種クラスで汎用的に使われる処理であれば、関数として、どこからでも呼べるようにしておくべきでしょう。
例えば、エラーメッセージのメッセージボックスを表示するための処理、とか、デバッグ用のログ出力を行う処理とか、ですね。
#デバッグログとか、日時出力とか行うので、それなりに長い処理になったりする。

このように、効率の悪くなるコードを関数やローカルメソッドにしてしまうプログラムの事を構造化プログラミングといいます。
いつぞや番外で触れましたけど。

見直せば、もっと構造化できそうなところもないではないですが、まあ、無理に構造化しても実行が遅くなったり、却って見にくくなってりします。
その他、Pythonでは、リスト内包表記が使えるので、あまり見ませんがループのネストが深くなる場合や、条件式の内側が、かなり大きくなる場合に、ローカルメソッドにして、処理そのもの流れを見やすくするなど、構造化プログラミングの手法としてあったりします。
#これもやり過ぎると実行速度に影響が出たりします。

いずれにせよ、ひとが見やすいように、処理を分割し、関数化、あるいはローカルメソッド化することにより、ムダを省き、バグが発生しにくくしましょうよ、ということを、今回の課題では伝えたかったんですが、ちとネタがディープ過ぎて伝わり難いというね。(笑)
これなら、リストの処理で、内部関数とか、そういうのをネタにした方が良かったかな。(^^;

まあ、例えて言うなら、毎日の通勤路、自分は階段を降りる時に、階段を登ってくるOLさんの胸の谷間を見るために、若干右よりに降りていく、すれ違う瞬間だけ、さっと左側に視線を走らせる、ということを毎日やっているとして、これをルーチン化して、無意識に行うのと同じようなことです。

意識して、毎度毎度やっていたのでは、不自然な動きになり、OLさんに警戒されてしまうでしょう。
そこを不自然にならないように、無意識に行えるように自分の中でルーチンとして一連の動きが処理されるようにまとめてしまう、ということです。
#例えがハイエンド過ぎて分かり難いか。
見事谷間を目撃できたら、その日はいいことがありますよ、的な占いのようなものでもあるかも知れません。
そのために、いかに自然に振舞うか。それがルーチン化の要でもあります。

プログラムだって同じなのです。毎度繰り返されるような処理はひとかたまりにして、ひとつのブロックとして、単に呼び出せば、それが実行される、という形にまとめておいた方が、いろいろと都合がいいわけです。
ルーチンを見直す時とかですね。
例えば、先の例だと、季節ごとにOLさんの服装も代わります。その場合、右によるか、左によるか、季節ごとの見直しが必要です。
しかもターゲットのOLさんがひとりでない場合、全体的に見直しになってしまいます。
そこで、きちんと関数として、まとめておくことで、季節ごとに、一箇所修正することで、無事に谷間を拝めるようになるのです。

朝の通勤時、OLさんの谷間を拝めるかどうか、それだけで朝の気分は違います。
それだけ、ルーチン化、関数化というのは重要なことなのです。

今回の課題で主張したかったのは、この点ですね。
「谷間を見逃すな」「同じ処理を2度書くな」ということです。

では、ぼちぼち、次のネタを考えることにしましょうかね。

2010年10月17日日曜日

課題2:スライドショーを作成する

えー、課題の2個目です。
いきなり敷居が高くなりますが、スライドショーを作成してみましょう。
プログラム的には、パスは固定で、任意のフォルダの画像を連続で表示する、というものになります。
とりあえず、壁紙のパスをソースでは設定していますが、まあ、自分の画像ファイルならどこでもいいと思います。

で、敷居が高いので、母体ソースを添付します。
ここからダウンロードしてください。

一応、コメントしていますが、この母体に関数(メソッド)を追加して、gtk.ImageであるimgSlideShowに画像を連続で表示していきます。
今はタイムアウトの時間を2秒で設定していますが、まあ、任意の秒数にしてみてもいいでしょう。

解答では、画面の横幅を固定して、画像のアスペクト比に合わせてウィンドウの高さを変更する、というのを予定していますが、まあ、そこまでは作りこまなくともよいです。
可能なら、挑戦してみましょう。
ここが参考になります。

とりあえず、完成後の画像イメージ。


まあ、正直、アスペクト比を保持したままのイメージ表示は敷居が高いので、画像ファイルをイメージとして表示する、までを目標にやってみてください。
なお、サンプルソースを見てもらえば解ると思いますが、画像ファイルのパスはself.imagePathにリストとして保持しています。
フォルダ階層をたどるサンプルにもなってますので、今後、使い途があるかも知れません。
まあ、これはこれで調べればすぐに解る類のものなのですが、今回は関数化というのをテーマとしたかったので、そこはサンプルコードとして出してしまいます。

そんじゃま、また、そのうち解答編で。

2010年10月11日月曜日

課題1:解答編

課題1の解答編です。
まあ、解答ってほどのものではありません。Gladeの使い方のおさらいです。




















トップレベルには、何もないウィンドウを使用し、MainWindowと名前を付けます。
コンテナは、水平コンテナでひとつだけにします。別にこれは垂直コンテナでも構いません。
最後の今回のポイントは画像コントロールを使用し、画像ファイルを指定する、というものです。
この画像コントロールにはプログラムから画像を指定することも可能ですが、Gladeから指定してしまう方が簡単でしょう。
注意すべきは、画像もプログラムと同じパス、つまり同じフォルダに置いておくこと、ってことでしょうかね。
なので、プログラム本体は大変シンプルになります。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import gtk

class ImageDisplay:
    
    def __init__(self):
        
        #GTK.Builderを構築
        wTree = gtk.Builder()
        #Gladeファイルの読み込み
        wTree.add_from_file("ImageDisplay.ui")
        #MainWindowオブジェクトを取得する
        mainWindow = wTree.get_object("MainWindow")
        #シグナルとシグナルハンドラをdic形式で作成する
        dic = {"on_MainWindow_destroy_event":self.on_MainWindow_destroy_event}
        #シグナルハンドラとシグナルを結びつける
        wTree.connect_signals(dic)
        #ウィンドウを表示する
        mainWindow.show_all()
    
    def on_MainWindow_destroy_event(self,widget):
        #GTKのメッセージループを終了する
        gtk.main_quit()

if __name__ == "__main__":
    #イメージ表示ウィンドウのコンストラクタを実行する(オブジェクトを作成する)
    ImageDisplay()
    #GTKのメッセージループを実行する
    gtk.main()

こんだけ。
☓ボタンで終了するために、MainWindowのシグナル、destroyをハンドルしているので、Gladeの方でも忘れずにシグナルを設定して置きましょう。

こんなの何に使うの?
という疑問もあるでしょうが。(笑)
これでウィンドウデコレーションを外せば、スプラッシュスクリーンに使えます。
その場合には、コンストラクタでタイマーをセットして、ある一定時間後にウィンドウを破棄する処理を走らせることになりますけど。
まあ、その辺は説明してないので、使い方い場合は別途、ということになるでしょうか。

コンストラクタの中身は、もはや定型的な処理です。
gtk.Builderを作成し、そのオブジェクトにGladeファイルを読み込み、MainWindowを取得して、表示を行う、これだけですね。

さて、とりあえず、おさらいということで、課題1を考えてみましたが。
次は関数定義と、その実行、関数にするべき場合とは、的な課題を考えたいものですがね。
まあ、なかなか課題を考えるのは難しいもので。先生とか偉いですねぇ。(笑)

まあ、とりあえず、プログラムなんて、おっぱいを揉むことから比べれば、圧倒的に簡単だし、試行錯誤もラクなので、色々と試したいことがあれば、どんどんとチャレンジしていけばいいと思います。

また、Pythonってのが、ある意味(構文的に)制限の多い言語なので、「これをやりたい」という場合に、それほど選択肢がなく、誰が書いても似たような書き方になる言語のひとつなので、ある意味「プログラミングの学習」には向いている言語ではないかと思います。

こういう書き方もある、こんな風にも書ける、そういう自由度の高い「表記が可能」な言語の場合、何が正しいのか解りにくい場合も多いと思いますけど。
#ロジックに関して言えば、こういう書き方もある、こうも書ける、はありえます。文法的な部分に制限が多いということですけどね。

さて、次の課題、ネタは何にしたものか。関数定義とか内部処理なんで、外側に現れにくいから、難しいんだよなー(笑)

2010年10月7日木曜日

課題編:課題1-Gladeを使って画像を表示してみましょうか。

まあ、これから課題編ということで。
これまでに学んだ(と思われる)知識をベースに、課題に挑戦して貰おうかと思います。(笑)

第一弾は、これ。

































Gladeを使って、画像を表示してみましょう。
なお、ついでに☓ボタンで終了出来るようにすることも忘れずに。

思い出した頃に、解答編を書きます。(笑)

ついでに、素材として、この画像もアップロードしときますが、別に表示する画像はなんでも構いません。
ちなみに、画像を表示する部分までは、Gladeにより処理できます。
プログラムは一切必要ありません。
※ヒント(笑)

では、挑戦してみてください。

おっと、忘れるところだ。
これ、素材。