term タグ別の記事一覧

「Pythonではバイナリファイルを操作したい時にバイナリ列をリストに変換して操作した後にまたバイナリ列に戻さなければならない」というのは不正確

 「Pythonでバイナリファイルを操作したい場合にファイルから読み出したバイナリ列をリストに変換しないとデータを自由に編集できないし、書き出し時に再度バイナリ列に変換しなければならないのでPythonは嫌い」という話を見かけたのですが、それは不正確だし適切な処理ではないです。

 そこではこの処理について、次のように記述されていました(適当に抜粋・改変しています)。

f = open("somefile.bin", "rb")
bin = f.read()
f.close()

data = []
for b in bin:
    data.append(b)

# ここでインデックスを使ったdata配列の操作を行う
# :
# :

f = open("output.bin", "wb")
f.write(bytearray(data))
f.close()

 このコードでは、データをbinという変数に読み込んだ後、dataというlistに入れて処理し、最後にそのlistをbytearray型に変換してからファイルに書き出しています。確かにこう書いてしまうと、配列への変換が冗長です。そもそもなぜこういった処理が必要になるかというと、open()でバイナリ形式での読み出し(“rb"フラグ)を指定した場合、読み出されたデータはbytes型のオブジェクトとして返されます。

$ python3
Python 3.7.5 (v3.7.5:5c02a39a0b, Oct 14 2019, 18:49:57) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> with open('testdata.bin', 'rb') as f:
...     bin_data = f.read()
... 
>>>
>>> type(bin_data)
 <class 'bytes'>

 bytes型では、インデックスを使ってその値を取得することができます。

>>> bin_data[48]
48

 一方で、bytes型はimmutableなオブジェクトなので、値を変更することはできません。

>>> bin_data[48] = 'A'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment

 しかし、実はPythonにはbytearrayというバイト列を扱うmutableな型もありまして、これを使えばbytes型と同様にインデックスを使った指定でデータを書き換えることができます。

>>> ba = bytearray(bin_data)
>>>
>>> type(ba)
 <class 'bytearray'>
>>>
>>> ba[48]
48
>>>
>>> ba[48] = 0x10
>>>
>>> ba[48]
16

 さらにこのbytearray型のオブジェクトは、write()メソッドの引数として指定することで直接ファイルに書き出せます。

>>> with open('output.bin', 'wb') as f:
...     f.write(ba)
... 
256

 ということで、先に挙げられていた「不満のあるコード」は次のように書き換えられます。

with open("somefile.bin", "rb") as f:
    data = bytearray(f.read())

# ここでインデックスを使ったdata配列の操作を行う
# :
# :

with open("output.bin", "wb") as f:
    f.write(data)

 このコードでも、ファイルから直接bytearray型のオブジェクトに読み込ませている訳ではない(bytes型オブジェクトを中継している)ため、結局バイナリ列をリストに変換しているじゃないか、と言われたらまあそうなんですが、それをバイナリ列に戻さなくても書き込みはできます、という話でした。

mod_wsgiでeasy_installとかでインストールしたPythonモジュールを読み込めない場合の対処

 WSGIを使ってWebアプリケーションを実装して、Apache+mod_wsgi環境で実行しようとした場合に、特定のモジュールがインポートできないという問題が発生することがある。具体的には、SELinuxが有効な状態で、easy_installなどでインストールしたPythonモジュールをインポートできないというものだ。この場合、以下のようにしてPythonのsite-packagesディレクトリのラベルを修正することで対応できる。

# restorecon -FRv /usr/lib/python2.7/site-packages/

Python 2.6/2.7のxmlrpclibでxml.parsers.expat.ExpatErrorが出た場合の対処

 Python 2.6系および2/7系のxmlrpclibで、サーバーからはどう見ても正しいXMLが返ってきているはずなのにxml.parsers.expat.ExpatErrorが出た場合の対処方法メモ。

 xmlrpclibでは、サーバーから受け取ったXMLデータを一定サイズごとに分割してXMLパーサーに投入する、という処理を行っている。このときXMLパーサーにExpatを使っていると、受け取ったデータが分割される位置によっては、不正なXMLと認識されて以下のようにエラーになる模様(DebianのPython 2.6.6とMac OS XのPython 2.7.2で確認)。Expatを使わないようにするとこの問題は発生しない。

  File "/Users/hylom/repos/anpanel/myxmlrpclib.py", line 559, in feed
    self._parser.Parse(data, 0)
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 30, column 114

 たぶん挙動的にExpatのバグのようだが、それを修正する気力もなかったので、とりあえずxmlrpclibでExpatを使わないようにすることで対処。たぶんパース速度は低下すると思われるが。具体的には、xmlrpclibをimportしたのち、XMLパーサーを取得するxmlrpclib.getparser関数を以下のようにして置き換える。

import xmlrpclib

# xmlrpclib's ExpatParser has bug, so do not use
org_getparser = xmlrpclib.getparser
def mygetparser(use_datetime=0):
    (p, t) = org_getparser(use_datetime)
    # if parser is ExpatParser, replace to SlowParser
    if isinstance(p, xmlrpclib.ExpatParser):
        p = xmlrpclib.SlowParser(p._target)
    return (p, t)
xmlrpclib.getparser = mygetparser

 ここでは、getparser関数の戻り値がExpatParserクラスのインスタンスだった場合、SlowParserクラスのインスタンスに置き換えるという処理をやっている。当然ながらかなりのdirty hackなので利用はおすすめしない。

Pythonコード中のSQL文インデントを考える

 Pythonコード内にSQL文を書くときどうすれば良いのか、いまいち答えが探せなかったのでググって見た話。

 ちなみに、今までは下記のような感じのコードを書いていた訳だが、これ見るからに分かりにくい。

# コード例その1
        cur.execute("""
          create table count (
            sid text,
            count int);""")

# コード例その2
        try:
            cur.execute("""insert into count ( sid, count )
                           values ( :sid, :count );""", d)
        except sqlite3.IntegrityError:
            cur.execute("""update count set sid = :sid, count = : count
                           where sid = :sid;""", d)
# コード例その3
        cmd = """select sid, title, date from stories where date >= ? and date < ? and sid in (
                 select sid from topics where topic == ? and sid in (
                 select sid from topics where topic == ? )) order by date
        """
        cur.execute(cmd, (begin_t, end_t, t1, t2))

 「SQL インデント」でググると、そういう話のネタが一杯出てくるでてくる。その中から拾ってみたのが下記。

 異端だけど、俺的コーディングルール SQL編 – suVeneのアレというのも参考になった。

 で、この辺をまとめたところ、だいたい以下のようなルールに落ち着いた。

  • SQLキーワードは大文字で
  • 括弧挟まれた部分はインデントレベルを+1する
  • カンマ、ANDの直後で改行
  • カンマやANDでつなげられている部分はなるべくキーワード部分でそろえる

 このルールで書いたコードは下記のような感じ。

# コード例その1
        cur.execute("""
            CREATE TABLE count (
                sid text,
                count int
            );
        """)

# コード例その2
        try:
            cur.execute("""
                INSERT INTO count (
                     sid,
                     count
                )
                VALUES (
                    :sid,
                    :count
                )
            """, d)
        except sqlite3.IntegrityError:
            cur.execute("""
                UPDATE count
                SET sid = :sid,
                    count = : count
                WHERE sid = :sid
            """, d)
# コード例その3
        cmd = """
            SELECT sid,
                   title,
                   date
            FROM stories
            WHERE date >= ? AND
                  date < ? AND
                  sid IN (
                      SELECT sid 
                      FROM topics
                      WHERE topic == ? AND
                      sid IN (
                          SELECT sid
                          FROM topics
                          WHERE topic == ?
                      )
                  )
            ORDER BY DATE
        """
        cur.execute(cmd, (begin_t, end_t, t1, t2))

 

 本当にこれで良いのかはまだ自信がないが、おおむね間違ってはいないと思う。ていうかコード内にSQL文を直書きせずO/Rマッパー使え、という話もあるが……。

要ログインのサイトにPythonのurllib2でアクセスする

 ログインが必要なWebサイトに対してスクリプトでページを取得/送信したい場合、まずログイン用のURLに対しログイン情報をPOSTしてCookieを取得する、という作業を行うのが一般的だ。しかし、Pythonのurllib2を利用すると簡単にPOSTは行えるのだが、なぜかCookieを取得できない、という問題が発生することがある。

 これはurllib2のurlopen()でURLを開き、帰ってきたオブジェクトのinfo()メソッド経由でSet-Cookieヘッダを取得しようとする場合に発生する。たとえばCMSを使用しているWebサイトで、管理用ページ以外に(正当な)Cookieを持たずにアクセスするとCookieなしでアクセスできるトップページ等にリダイレクトされる、というケース。多くのCMSではログインに成功すると管理ページトップにリダイレクトされるのだが、urllib2のデフォルト設定ではCookie処理を行ってくれない&リダイレクトを自動的に処理してくれるため、「ログイン成功→(Cookieを返すがurllib2はCookieを保存せず)→管理ページトップにリダイレクト→Cookieがないので公開ページのトップにリダイレクト→Cookieは受け取れず」という事態になってしまうことがある。

 この場合、urllib2.HTTPCookieProcessorを使ってCookie処理を行えばよいのだが、なぜかurllib2のドキュメントにはこのクラスの解説が無い。例にも上がっていない。ということで途方にくれるわけだが、実はCookieを扱うcookielibのほうに使い方の例が載っていたりする。

import cookielib
# : 
# (このへんでurlやencoded_data、headersを準備)
# :
req = urllib2.Request(url, encoded_data, headers)
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
resp = opener.open(req)

 取得したCookieはCookieJarオブジェクトに保存されるので、必要に応じて適宜取り出せばOK。かわりにFileCookieJarオブジェクトを使えばファイルへのsave/loadも可能。

Pythonで被はてなブックマーク数を取得する

 Pythonのxmlrpclibモジュールで、指定したURLの被はてなブックマーク数を取得する例。簡単ですね。

 server.bookmark.getCount()はURLをキー、被はてブ数の値とするdictionaryの形で返してくれるので、返ってきたデータの参照も楽勝です。URLの一覧を引数の形で与えなければならないのがなんか引っかかりますが……。

#!/usr/bin/env python

import xmlrpclib


def main():
    uri = "http://b.hatena.ne.jp/xmlrpc"
    server = xmlrpclib.ServerProxy(uri)
    urls = ("http://sourceforge.jp/magazine/10/04/26/0244255",
             "http://sourceforge.jp/magazine/10/04/27/0326224",
             "http://sourceforge.jp/magazine/10/04/30/0243232")

    t = server.bookmark.getCount(*urls)
    for item in t:
        print item, t[item]

if __name__ == "__main__":
    main()

Pythonのwith構文と__enter__、__exit__

 Pythonのwith構文がいまいち掴めなかったので、ざっとまとめてみた(いまさらながら)。ドキュメントはPython リファレンスマニュアルの7.5 with 文にある。

 withを使ったコード例は、下記のような感じ。

c = ClassHogeHoge()
with c:
    c.foobar()

 上記のコードは、下記と等価となる。

c = ClassHogeHoge()
c.__enter__()
c.foobar()
c.__exit__()

 つまり、withに続くインデントブロックを実行する前に指定したオブジェクトの「__enter__()」メソッドを呼び出し、実行後に「__exit__()」メソッドが暗に呼び出される、という仕組み。

 __enter__()と__exit__()の定義は、Python リファレンスマニュアルの3.4.9 with文とコンテキストマネージャにある。__enter__()の引数はselfのみだが、__exit__()はself、exc_type、exc_value、tracebackの4つの引数をとる。withに続くインデントブロックが正常に実行された(つまり、例外が送出されなかった)場合、(self以外の)引数にはNoneが与えられる。なにか例外が発生した場合、その例外に関する情報が与えられるらしい。

 また、「with hogehoge as foo:」のような形でwith文を利用する場合、__enter__()の戻り値がfooに代入される。__exit__()の戻り値は例外処理の伝搬制御に使われ、Falseの場合例外が発生た場合でも例外を伝搬させず、Trueを返すと例外が伝搬されるとのこと。

 下記、使用例。

class CacheDB(object):
    DB_FILE = "database/db_dat"

    def __init__(self):
        self.con = None
        self.cur = None

    def __enter__(self):
        self.con = sqlite3.connect(self.DB_FILE)
        self.cur = self.con.cursor()

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            self.con = None
            self.cur = None
            return False
        self.con.commit()
        self.cur.close()
        self.con.close()
        self.con = None
        self.cur = None
        return True

    def add(self, foo, bar, hoge):
        try:
            self.cur.execute("""insert into data ( foo, bar, hoge )
                           values (?, ?, ?);""", (foo, bar, hoge))

def main():
    usage = "%s logfile" % sys.argv[0]
    db = CacheDB()
    try:
        fname = args[0]
    except IndexError:
        sys.exit(usage)

    f = open(fname, "r")
    with db:
        for l in f:
            term = l.strip().decode("utf-8").rsplit("\t", 3)
            db.add(foo=term[0],
                   bar=term[1],
                   hoge=term[2])

if __name__ == '__main__':
    main()

 タブ区切りのデータファイルを1行ずつ読んでデータベースに突込む、という処理。withを使うことで、データベースアクセスの準備→データ挿入→コミットという流れをきれいに実装できました。

日本語リファレンスには書いてない話:urllibとurllib2の違いってなんだ

 Pythonでは、HTTPやFTPなどでファイルの送受信をするモジュールとして「urllib」と「urllib2」が用意されている。使い方も似ていて、どちらも引数としてURLを与えてurlopen()関数を呼び出すと自動的に対応したプロトコルでURLにアクセスしてデータを読み出せる、というものである。しかし、似たような名前で似たような機能を持つこの2つ、何が違うのかが明確にはドキュメントに記述されていない。そのためどちらを使うべきか迷って、そのたびにGoogleのお世話になるという状況だったのでざっとまとめてみよう。

外から見える違いとしては、urlopen()の引数としてurllibはURL文字列のみを受け付けるのに対し、urllib2ではurllib2.Requestクラスを受け取ることができる、という点がある。urllib2.Requestクラスはリクエストを抽象化したクラスで、これを利用することでたとえばHTTPでのデータ送受信の際、任意のHTTPヘッダーを送信させるようなことが可能になる。また、urllibでは使用するproxyを直接引数で指定できるが、urllib2ではRequestオブジェクトで指定することになる。

 ということで、ただHTTPやFTPでデータを取得したいだけならurllibでもurllib2でもどちらでも対して変わらない、HTTPヘッダーをカスタマイズしたいならurllib2を使え、という話になる。あと、urllibにはurlretrive()という関数があったり、quote()/unquote()/urlencode()などの便利そうなユーティリティ関数がある、というくらい。

独自プロトコルを実装したい場合は?

 いっぽう、挙動をカスタマイズしたい、独自プロトコルを実装したい、という場合は話が変わってくる。urllibもurllib2も独自プロトコルへの対応などのカスタマイズが可能なのだが、urllibはFancyURLopenerクラスの派生クラスで実装を行うのに対し、urllib2はBaseHandlerクラスの派生クラスで実装する。

 まず、urllibの場合。ドキュメントに記載されている例だが、たとえばUser-Agentを変更したい場合、下記のようにFancyURLopenerの派生クラスを作り、その初期化時に指定したいUserAgentをversion変数に代入するようにする。そして、この派生クラスのインスタンスをurllib._urlopenerに代入してやる。

import urllib

class AppURLopener(urllib.FancyURLopener):
    def __init__(self, *args):
        self.version = "App/1.7"
        urllib.FancyURLopener.__init__(self, *args)

urllib._urlopener = AppURLopener()

 これにより、以後はurllib.urlopen()などを呼び出した際、urllib._urlopener()で指定したインスタンスを使用してHTTP/FTPなどの処理が行われるようになる。

 ちなみに、FancyURLopenerとURLopenerの関係だが、HTTP/HTTPS/FTPなどの基本的な通信/データ取得処理はURLopener内で実装されており、FancyURLopenerはそれに各種HTTPレスポンスコードへの対応やリトライといった処理を実装したサブクラスとなっている。

 またドキュメントには記述されていないが、URLopenerにはaddheader()というメソッドが用意されており、ヘッダー名をキー、与えるデータを値にした辞書を引数として与えることで、任意のヘッダーを追加できる。

def addheader(self, *args):
        """Add a header to be used by the HTTP interface only
        e.g. u.addheader('Accept', 'sound/basic')"""
        self.addheaders.append(args)

 たとえばURLopenerの__init__()内では、次のようにしてUser-Agentを指定している。

self.addheaders = [('User-Agent', self.version)]

 なおURLopenerでは、urlopen()でHTTP/HTTPSのURLを与え、かつ第2引数が非Noneの場合(つまり、なんらかのデータを与えた場合)は「Content-Type: application/x-www-form-urlencoded」でのPOSTリクエストを利用するようにハードコーディングされている。そのため、ほかの形式でデータをPOSTしたい場合(ファイルを送信するなど)は、独自のURLopenerを作成する必要がある。

urllib2で独自プロトコルを使うには?

 いっぽうurllib2で独自のプロトコルを扱いたい場合、まずプロトコルを実装したBaseHandler()派生クラスを用意し、続いてそれを引数として与えてbuild_opener()を呼び出してURLハンドラを作成、最後にそれをinstall_opener()引数に与えて登録、という手順となる。下記はドキュメントに記載されている例だ。

import urllib2
# ベーシック HTTP 認証をサポートする OpenerDirector を作成する...
auth_handler = urllib2.HTTPBasicAuthHandler()
auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib2.build_opener(auth_handler)
# ...urlopen から利用できるよう、グローバルにインストールする
urllib2.install_opener(opener)
urllib2.urlopen('http://www.example.com/login.html')

 なお、urllib2の(デフォルトの)HTTP/HTTPSハンドラでも、urlopen()の第2引数が非Noneの場合(つまり、なんらかのデータを与えた場合)は「Content-Type: application/x-www-form-urlencoded」でのPOSTリクエストを利用するようにハードコーディングされている。そのため、ほかの形式でデータをPOSTしたい場合(ファイルを送信するなど)は、やっぱり独自のハンドラを作成する必要がある。

 ちなみに、Python3系ではPython2系のurllibは廃止され、urllib2もurllib.requestという名称に変更されている。ということで、Python3系を見据えるならurllibではなくurllib2を利用するのが望ましいようだ。

Mako Templaters for Pythonメモ1:Makoってなに?

 最近Pythonのテンプレートエンジン「Mako」を触ってるんだけど、日本語の情報が全然ないのでまとめてみる。

Mako公式Webサイト

Mako公式Webサイト

Makoは「Hyperfast and lightweight templating for the Python platform.」(Pythonプラットフォーム向けの超高速で軽量なテンプレートエンジン)だ。Pythonのテンプレートエンジンとしては、Python標準ライブラリに含まれているstring.Templateや、WebフレームワークのDjangoに組み込まれているDjangoテンプレートエンジン、そしてCheetahなどが知られているが、Makoはそれらよりも高速で、テンプレート内にPythonコードを埋め込む機能や、キャッシュ機構などを備えてるのが特徴だ。また、文法もPython風であり習得しやすいのも利点だろう。

 いっぽう、特に大きな欠点は(いまのところ)見つかっていないのだが、日本語環境で利用する場合は文字コードをうまく扱うように適切にオプションを与える必要がある。

 ちなみに、MakoのWebサイトにはPython向けテンプレートエンジンのパフォーマンス比較が掲載されているのだが、ほかのテンプレートエンジンと比べてMakoは同等レベル以上に高速、という結果が出ているようだ。

Makoのインストール

 Makoはダウンロードページから行える。配布されているtar.gz形式のソースコードをダウンロードしてインストールできるほか、Pythonモジュール用のインストールマネージャ「easy_install」を利用してもインストールできる。

 ソースコードからインストールする場合は、ダウンロードしたアーカイブを展開し、次のように実行する。

# python setup.py install

 ちなみに、makoはすべてPythonコードで記述されているため、基本的にはPythonが動く環境であればどのプラットフォームでも動作するはずだ。

 つづく。

pyblosxom用のブログ記事をWordPressにインポートする

 以前このブログはPythonで書かれたブログシステム「pyblosxom」を使って運用していたんだけど、機能拡張が面倒臭かった(&色々細かいところが気にくわなかった)ので先日WordPressに移行しました。そこで面倒だったのが記事の移行。WordPressではMovableTypeとかBloggerとかからの移行ツールは充実しているんだけど、pyblosxomからの移行ツールは公式には用意されていません。また、参考情報としてWordPressの公式サイトで紹介されていたblosxomからの移行スクリプトはURLやタグ情報を移行してくれなかったため、結局自前で変換スクリプトを用意することに。

 幸い、WordPressのインポート/エクスポートで使われるWXL(WordPress XML)形式のファイルを簡単に生成できるモジュールをGoogle様が提供しているのを発見、そいつを使ってお気楽に作成できました。ということでここでご紹介。

 スクリプト本体はgithubで公開しているので必要な方は適宜どうぞ。「pyblosxom2wp.py」が本体です。このスクリプト内の頭で宣言している変数「title」にブログのタイトル、「link」にブログのリンクURL、「baseurl」をにBase URLが入るように書き換えて、次のように実行すると標準出力経由でインポート用XMLファイルが出力されます。

./pyblosxom2wp.py <pyblosxomの記事ディレクトリ> > output.xml

あとはそいつをWordPressのインポート機能で読みこめばOK、のはず。これで、カテゴリやタグ、URLについてもできるだけ保持する形でインポートが可能です。

Pythonネタ:unittestを使う

 Python標準のユニットテスト機能、「unittest」の使い方メモ。

目的

 unittestはPythonで作成したクラスの特定の関数や、機能の動作確認に利用できる機能だ。詳しくは ドキュメントを読めばすぐに分かる が、unittestクラスの派生クラスを作り、そのクラスの関数としてテストコードを記述してやると、簡単にユニットテストができる、というもの。

 メインの実装コードにprint文などを挿入したり、テストコードを挿入しても良いのだが、それだとテストコードの再利用が難しかったり、いったんバグ修正を行ってテストコードを削除した後に再度バグが発生したりした場合に二度手間になったりする。そのため、テストコードはなるべくunittestにまとめて記述しておくとデバッグや実装、テストが楽になりますよ、というお話。

使い方

 基本的な使い方はこれまたオンラインドキュメントにあるのだが、自分は下記の形をよく使っている。

import random
import unittest

class TestSequenceFunctions(unittest.TestCase):
    def setUp(self):
        # ここに各テスト関数を実行する前に呼び出す処理を書く。
        # 通常は共通のデータの準備とかを書くことが多い


   def tearDown(self):
        # ここに各テスト関数を実行した後に呼び出す処理を書く。
        # 通常は共通のデータの後片付けとかを書くことが多い


    def test_hogehoge(self):
        """test for hogehoge テストの内容をコメントに入れる"""
        # テストコード1をここに書く


    def test_foobar(self):
        """test for foobar テストの内容をコメントに入れる"""
        # テストコード2をここに書く


# do unittest
# テストオブジェクトを作成
suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)

# テスト実行。出力するメッセージレベルはverbosity引数で設定できる
unittest.TextTestRunner(verbosity=2).run(suite)

 テストコード内でテストが期待したとおりの処理を行っているかどうかは、unittest.TestCaseクラス内で用意されているasert/fail関数を使うのが好ましい。詳しくは「pydoc unittest」等で確認できるが、たとえば二つの引数の値が等しくない場合にエラーを出すには「assertEqual(引数1、引数2、エラーメッセージ)」関数を使う。

 そのほか、unittestにはレポート機能などもあるが、基本的には上記さえ押さえておけば事足りるはず。

Pythonのクラスの挙動を調べる:class構文の外でクラスにメンバ関数を追加(2)

 今度は、別のモジュールで定義したクラスにメンバ関数を追加してみる。

[Macintosh:~]$ python
Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

   # importすることで定義されたクラスは、その名前空間内に閉じこめられる 

>>> import foobar
>>> print foobar.FooBar
<class 'foobar.FooBar'>

   # 名前空間が異なっていても、名前空間さえ指定すればメンバ関数を追加できる 

>>> f = foobar.FooBar()
>>> f.show_name()
I'm FooBar.
>>> def rename(self,new_name):
...     self.name = new_name
...
>>> foobar.FooBar.rename = rename
>>> f.rename("john")
>>> f.show_name()
I'm john.

 大体予想通りの結果ですな。

PythonでCGI経由でファイルアップロード

 Webベースで記事作ったりサイトデザインしたりしていると、多量のファイルをアップロードする機会も多々あるのでファイルを自動アップロードするスクリプトを書きたい、という話。

 とりあえずググったら「 残高照会メモ: pythonでアップロード 」が出てきたのだが、自前でMIMEエンコーディングしなきゃいけないのがちょっとアレだ。

 ちなみにPerlだと下記のような感じでいける。

sub post_attachment {
    my $self = shift @_;
    my %args = @_;

    my $file_name = $args{file};  # filename

    my $url = "アップロード先URL";
    my $ua = LWP::UserAgent->new( 'agent' => "適当なUserAgent文字列", );
    my $req = HTTP::Request::Common::POST $url, Content_Type => 'form-data',
    Content => [
				file_content => ["$file_name"],
				description => "",
				op => "addFileForStory",   # form "op"の値
				sid => $sid,               # form "sid"の値
				Submit => "Submit",        # form "Submit"の値
	];

    my $resp = $ua->request( $req );

    if( $resp->is_error ) {
	return 0; #print "upload $file_name: failed.\n";
    } else {
	return 1; #print "upload $file_name: succeed.\n";
    }
}

 これをPythonでやりたい。ということで続く。