はてブロ@ama_ch

https://twitter.com/ama_ch

今日のPython

アトリビュートやメソッドのカプセル化

p.273
Pythonでアトリビュートやメソッドをカプセル化するためには、2つの方法がある。
・アトリビュート名やメソッド名の先頭にアンダースコアを1つ(_)つける
名前の先頭にアンダースコアがひとつついたアトリビュートやメソッドは、クラス内部だけで利用するためにある、というルールのようなものがある。ただし、暗黙の了解なので、実際外部からアクセスできなくなる訳ではない。
・アトリビュート名やメソッド名の先頭にアンダースコアを2つ(__)つける
アンダースコアをふたつつけると、クラスの外部からアクセスができなくなる。


例.

>>> class Klass:
...     sizeA = 10
...     _sizeB = 20
...     __sizeC = 30
... 
>>> a = Klass()
>>> a.sizeA
10
>>> a._sizeB
20
>>> a.sizeC  #アクセスできない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Klass instance has no attribute 'sizeC'

クラスの継承

p.275
Pythonのクラスは多重継承を行うことができる。
クラスの継承を行うには、class文でスーパークラスを指定する。クラス名の後に丸カッコをつけ、その中にスーパークラス名を記述する。多重継承をする場合、コンマで区切ってクラス名を記述する。
class クラス名(スーパークラスA, スーパークラスB):

メソッドのオーバーライド

p.276
Pythonでは、継承を行う際、初期化を行う__init__()メソッドも上書きされるので注意。サブクラスで__init__()メソッドを記述した場合、スーパークラスの同メソッドが上書きされる。


継承の例.

class Blogmark(Bookmark):
    def __int__(self, title, url, rss):
        # インスタンス初期化用メソッド
        self.rss = rss
        Bookmark.__init__(self, title, url)  #親クラスを記述

super()という組み込み関数を使うとよりスマートに記述できるらしいけど、よくわからなかった。

クラス定数

p.278
クラス内で定数を定義する。

BLANK = 0
WHITE = 1
BLACK = 2

って感じ。

クラスとモジュール

Bookmark.pyをモジュールとして扱う。
p.280

>>> import Bookmark
Title : ama-ch's Blog, URL : http://d.hatena.ne.jp/ama-ch/
>>> # モジュール名.クラス名でオブジェクトを作成
>>> b = Bookmark.Bookmark(u"タイトル", "http://hoge.hoge/")
>>> b.url
'http://hoge.hoge/'

魔法の関数dir()

p.283
組み込み関数dir()で遊ぼう!
http://www.python.jp/doc/release/lib/built-in-funcs.html

>>> class aklass:  #簡単なクラスを定義
...     def __init__(self):
...             self.spam = 1
... 
>>> i = aklass()  #インスタンスを作る
>>> dir(i)  #アトリビュートのリストを表示
['__doc__', '__init__', '__module__', 'spam']
>>> i.egg = 1  #インスタンスにアトリビュートを追加
>>> dir(i)  #さらにアトリビュートのリストを表示
['__doc__', '__init__', '__module__', 'egg', 'spam']

dir()関数を使うとアトリビュートのリストを得ることができる。ここで、__init__()メソッドのようなメソッドも一緒に表示されている。つまり、「メソッドもアトリビュート」である。

アトリビュートとしてのメソッド

Pythoでは、「spam.egg」のように、インスタンスオブジェクトからドットで区切って記述できるものは、すべてアトリビュートとして扱われる。メソッドもその例外ではない。
Pythonでは、メソッド名の後ろに丸カッコをつけるとメソッドが呼び出され、丸カッコをつけないと変数のように扱える。


例.

>>> class atomklass:  #クラスを定義
...     def foo(self):  #メソッドを定義
...             print "this is foo method!"
... 
>>> i1 = atomklass()  #インスタンスを作成
>>> i2 = atomklass()  #もういっこ作成
>>> i1.bar = i1.foo  #メソッドを新しいアトリビュート(クラス変数)に代入
>>> i1.bar()  #コピーしたメソッドを呼び出す
this is foo method!
>>> i1.bar  #アトリビュートとして呼び出す
<bound method atomklass.foo of <__main__.atomklass instance at 0x69cd8>>
>>> i2.bar()  #このインスタンスではエラー
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: atomklass instance has no attribute 'bar'  #アトリビュートエラー。存在しないメソッド

すべてがオブジェクト

p.286

  • 型があり、アトリビュートを持つのがPythonのオブジェクトである
  • Pythonではすべてがオブジェクトである

ここ大事!
数値やリストは組み込み型のオブジェクト。クラスから作られるインスタンスも当然オブジェクト。他にも、関数、メソッド、モジュール、クラス・・・プログラムで使うすべてのものがオブジェクトである。

オブジェクトと型

p.286 組み込み関数type()でいろんなオブジェクトの「型」を調べる

>>> type(1)
<type 'int'>
>>> type("abc")
<type 'str'>
>>> type([1])
<type 'list'>
>>> type(("abc"))
<type 'str'>
>>> type((1, 2, "ab"))
<type 'tuple'>
>>> type(u"あいうえお")
<type 'unicode'>
>>> import sys
>>> type(sys)
<type 'module'>


また、あるオブジェクトがどの型に属するのかを調べるにはisinstance()という関数を使う。オブジェクトと型を引数として渡し、オブジェクトの型が引数の型と一致するか、親の関係の時にTrueを返す。

>>> isinstance(1, type(1))
True
>>> isinstance(1, int)
True
>>> isinstance(1, str)
False
>>> isinstance(u"あいう", unicode)
True
>>> isinstance(u"あいう", basestring)  #basestringはstrおよびunicodeのスーパークラス
True

issubclass()という組み込み関数に2つの型を与えると、ある型が、他の型のスーパークラスに該当するかどうか調べられる。

>>> issubclass(str, basestring)
True
>>> issubclass(basestring, str)
False
>>> issubclass(int, str)
False

オブジェクトとアトリビュート

p.288
dir()関数を使うと、オブジェクトが持つアトリビュートの一覧を得られる。

>>> dir("abc")
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> dir([1,2])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

メソッドや関数のようなオブジェクトは、とくに呼び出し可能オブジェクトと呼ばれる。

オブジェクトと変数

p.290
関数を変数に代入し、丸カッコをつけて元の関数と同じように扱うことができる。

>>> import urllib  #urllibをインポート
>>> u = urllib     #モジュールを変数に代入
>>> o = u.urlopen  #urlopen()関数を変数に代入
>>> #代入した変数を呼び出す
>>> o("http://python.jp/Zope").read()[:20] + "..."  #URLの中身を20文字まで読み込み、"..."をつなげる
'<!DOCTYPE HTML PUBLI...'

新スタイルクラスの機能

p.293
__slots__を使うと、アトリビュートを制限できる。

>>> class Klass(object):  #新スタイルクラスを定義
...     __slots__ = ["a", "b"]  #アトリビュートを制限
...     def __init__(self):
...             self.a = 1  #アトリビュートaを作成
... 
>>> i = Klass()  #インスタンスを作成
>>> i.a  #アトリビュートaを確認
1
>>> i.b = 2  #アトリビュートbを追加
>>> i.b
2
>>> i.c = 3  #アトリビュートcは追加できない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Klass' object has no attribute 'c'

組み込み型を継承する

p.306

辞書型を継承する
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' strdict.py
辞書型を継承し、文字列のみをキーとして設定できる辞書の作成
'''

class StrDict(dict):
    def __init__(self):
        pass

    def __setitem__(self, key, value):
        # 特殊メソッドをオーバーライド
        # keyが文字列型以外なら例外を発生
        if not isinstance(key, str):
            # キーが文字列でない場合には例外を発生
            raise ValueError("Key must be str or unicode.")
        # スーパークラスの特殊メソッドを呼び出し、キーと値を設定
        dict.__setitem__(self, key, value)

実行結果

>>> from strdict import StrDict
>>> d = StrDict()
>>> d["spam"] = 1  #キーを使って要素を追加
>>> d["spam"]
1
>>> d[1] = 1  #数値のキーで要素を追加するとエラー
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "strdict.py", line 16, in __setitem__
    raise ValueError("Key must be str or unicode.")
ValueError: Key must be str or unicode.
>>> d.keys()  #辞書のメソッドも利用できる!
['spam']

特殊メソッドの__setitem__()をオーバーライドすると、メールアドレスだけをキーとして登録できる辞書などが簡単に作れる。


らしいので、作ってみた!

#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' maildict.py
辞書型を継承し、メールアドレスのみをキーとして設定できる辞書の作成
'''

class MailDict(dict):
    def __init__(self):
        pass

    def __setitem__(self, key, value):
        # 特殊メソッドをオーバーライド
        # keyが文字列型以外なら例外を発生
        if not isinstance(key, str):
            raise ValueError("Key must be str and e-mail address.")
        # 文字列型のとき、'@'を含まないと例外を発生
        elif key.find('@') < 0:
            raise ValueError("Key must be e-mail address.")
        # スーパークラスの特殊メソッドを呼び出し、キーと値を設定
        dict.__setitem__(self, key, value)

実行結果

>>> from maildict import MailDict
>>> d = MailDict()
>>> d["ama-ch"] = 1  #文字列(@を含まない)をキーとして登録
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "maildict.py", line 18, in __setitem__
    raise ValueError("Key must be e-mail address.")
ValueError: Key must be e-mail address.
>>> d[123] = 1  #数値をキーとして登録
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "maildict.py", line 16, in __setitem__
    raise ValueError("Key must be str and e-mail address.")
ValueError: Key must be str and e-mail address.
>>> d["hoge@fuga.com"] = 1  #@を含むと登録できる
>>> d
{'hoge@fuga.com': 1}
>>> d["@"] = 2  #"@"だけのキーでも登録できるけど・・・
>>>

Pythonの例外処理

例外を捕まえる

p.310 ファイル名を引数として渡し、サイズを調べる

#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' filelen.py
ファイル名を引数として渡し、サイズを調べる
'''
import sys

for fn in sys.argv[1:]:
    try:
        f = open(fn)
        print fn + " : %d Bytes" %  len(f.read())
    except IOError:
        print u"%sというファイル名は存在しません" % fn

実行結果

$ python filelen.py test.py Bookmark.py aaa.py
test.py : 201 Bytes
Bookmark.py : 1348 Bytes
aaa.pyというファイル名は存在しません

try〜exceptで囲まれたブロックで例外が発生すると、プログラムの実行がスキップされ、except以下のブロックに制御が移る。


p.311 例外を処理するための書式

書式 説明
except: すべての例外を受け取り、例外発生時の処理を行う
except 例外クラス名: クラスを指定して、特定の例外だけを受け取る。丸カッコで囲みコンマで区切ることで、例外クラスを複数列記できる。
except 例外クラス名:, 変数名: 例外クラスと、例外オブジェクトを受け取る変数名を指定する。例外オブジェクトが代入された変数を使って、より細かな情報を得られる。
else: 例外が発生しなかった場合の処理を行うときに利用する。
finally: 例外が発生してもしなくても、実行するブロックを記述するときに利用する。
例外を発生(raise)させる

p.312
raise文を使い、自分で作ったクラスのエラーなどを伝えることができる。方法は2種類あり、

  • 文字列例外を発生させる - raise文のあとにクオーテーションで囲んだ文字列オブジェクトなどを添える
  • クラス例外を発生させる - 例外クラスから作った例外オブジェクトを添えて例外を発生させる。

標準ライブラリを使う(2)

base64モジュール

p.332

>>> import base64
>>> m = base64.b64encode("ama-ch")  #文字列をエンコード
>>> m
'YW1hLWNo'
>>> n = base64.b64decode(m)  #結果をデコード
>>> n
'ama-ch'  #復元!
urlparseモジュール

p.340 URLを要素に分割するurlparse()

>>> import urlparse
>>> url = "http://some.host/path/to/file.txt?spam=egg#ham"
>>> urlparse.urlparse(url)
('http', 'some.host', '/path/to/file.txt', '', 'spam=egg', 'ham')
スレッド

p.371 スレッドで稼働するWebサーバ:Pythonで立ち上げたカレントディレクトリにあるファイルを配信する

#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' threadedhttpserver.py
スレッドで稼働する簡易Webサーバ
'''
from BaseHTTPServer import *
from SimpleHTTPServer import *
from threading import Thread

class ThreadedHTTPServer(Thread):
    def __init__(self, port=8080, **kwargs):
        """ Thread対応サーバオブジェクトの初期化
        """
        self.httpd = HTTPServer(("", port), SimpleHTTPRequestHandler)
        self.httpd_running = False
        Thread.__init__(self)

    def run(self):
        """ スレッドのコードを実行
        """
        self.httpd_running = True
        while self.httpd_running:
            self.httpd.handle_request()
        del self.httpd

    def stop(self):
        """ スレッドの実行を停止するためのフラグを立てる
        """
        self.httpd_running = False

実行結果(ターミナル)

>>> from threadedhttpserver import ThreadedHTTPServer
>>> s = ThreadedHTTPServer()
>>> s.start()
>>> localhost - - [13/May/2008 17:54:00] "GET / HTTP/1.1" 200 -
localhost - - [13/May/2008 17:54:00] code 404, message File not found
localhost - - [13/May/2008 17:54:00] "GET /favicon.ico HTTP/1.1" 404 -
localhost - - [13/May/2008 17:54:03] code 404, message File not found
localhost - - [13/May/2008 17:54:03] "GET /favicon.ico HTTP/1.1" 404 -
localhost - - [13/May/2008 17:56:09] "GET / HTTP/1.1" 200 -
localhost - - [13/May/2008 17:58:05] "GET / HTTP/1.1" 200 -

>>> s.stop()

実行結果(ブラウザ)