Python Tips

このページは Python 2.3 くらいのときに書かれたものです。

内包表記とλ式

>>> fs = [ lambda: i for i in range(8) ]
>>> fs[2]()
7

各 i ごとにクロージャが生成されるのではなく、ひとつのクロージャで i の値が書き換えられるため、このような結果になる。これは期待した動作ではないと思う。

同じ問題は for ループでも起こる。

>>> for i in range(8):
...     fs.append( lambda: i )
...
>>> fs[2]()
7

これは、デフォルト引数をうまく使えば回避できる。

>>> fs = [ lambda i=i: i for i in range(8) ]
>>> fs[2]()
2

デフォルト引数

これは Tutorial にも載っているけど。

>>> def f(xs = []):
...     xs.append(0)
...     print xs
>>> f()
[0]
>>> f()
[0, 0]

GC

CPython の GC は参照カウントを主に用いているので、(循環参照が発生しなければ)ファイルのクローズなどをデストラクタに任せることができる。

>>> data = file("hoge", "r").read()

上のプログラムで、 file オブジェクトは次の行が実行される前に必ず回収され、ファイルのクローズも行われる、ということ。

最近は CPython 以外の実装も普及してきて、それらの処理系が参照カウントを使っている保証はないので、 with 文などを用いて確実にデストラクトされるようにすべきです。すいません。

デストラクタを持つオブジェクトが循環参照に含まれる場合の GC の動作は言語によってまちまちだが、 Python では原則としてそれらのオブジェクトは開放しない。回収されなかったオブジェクトは gc.garbage に保存される。

http://www.python.jp/doc/nightly/lib/module-gc.html

modulo

>>> (-3) % 2
1
>>> 3 % (-2)
-1
>>> (+1) / 3
0
>>> (-1) / 3
-1

負数に対する剰余演算の結果は C などでは環境依存で、例えば x86 (たぶんほとんどのアーキテクチャでそうなんだろうけど)なら (-3) % 2 == -1 だけど、 Python では上記のように少し気の利いた動作になっている。これと consistent なように、除算に関しても 0 に近い整数に丸められるのではなく、小さい整数に丸められる(!)。

これ、便利なことも多いのだけど、代償として、

(-a) / b != -(a / b)
(-a) % b != -(a % b)

なので、気を付けるべし。怖っ。

StopIteration

イテレータの next() メソッドは、列挙する要素がなくなって StopIteration 例外を送出したあとは、以降の呼び出しでもずっと例外を送出しつづけるように実装すべき。

http://www.python.jp/doc/nightly/lib/typeiter.html

class 周りの動作が謎

staticmethod, classmethod, property とか、 __metaclass__ とか。

…これらは v2.2 で導入された new-style class に関連している。詳細は Unifying types and classes in Python 2.2 にまとまっている。言語ヲタクが Python を習得する際には必修事項 :-)

定数文字列の最適化

>>> a = "short"
>>> b = "short"
>>> a is b
True
>>> a = "long text"
>>> b = "long text"
>>> a is b
False

短い文字列は最適化でまとめられるらしい。

組み込み関数の範囲内で簡易文字コード判別

encodings = [
    "iso-2022-jp",
    "utf-8",
    "euc-jp",
    "cp932",
]


def detect( text ):
    bestScore = -1
    bestEnc = None
    for enc in encodings:
        try:
            unicode( text, enc )
        except UnicodeDecodeError, err:
            if err.end > bestScore:
                bestScore = err.end
                bestEnc = enc
        else:
            return {
                "encoding": enc,
                "confidence": 1.0,
            }

    return {
        "encoding": bestEnc,
        "confidence": bestScore / (bestScore + 2.0),
    }

Download: chardet.py

chardetという素晴らしい文字コード判定ライブラリがあるが、ちょっと使うには大げさで、速度もかなり遅い。文字コードがある程度限定されているなら、 unicode() 関数で成功するまで変換してみるという方法で文字コード判別ができる。 chardet よりずっと速かったり。

上のコードは chardet とインターフェイスを合わせてあるので、 chardet をインストールするかわりに chardet.py を置くだけで使える。ただし confidence の値は適当気味。

複数の *.py を直接実行可能な 1 ファイルにまとめる

CPython は zip ファイルをインポートできて (zipimport) 、さらに __main__.py がアーカイブ内にあると python archive.zip したときにそれが実行される。また一方で zip ファイルはヘッダを尻に持っていて、ファイルの先頭にゴミがついていても問題なく読み込める。そこで、

$ echo "#!/usr/bin/env python" > executable
$ app.zip >> executable
$ ./executable

とすれば、直接実行可能なファイルが作れる。

asyncore を socket 以外で使う

ドキュメントを読むと、 asyncore は socket にしか使えないように思えるが、実際にはファイルオブジェクト全般に使える。基本的には、 asyncore.socket_map にファイルディスクリプタとオブジェクトを追加しておくと、適当なメソッドが呼ばれるシンプルな仕組み。以下 stdin での例:

import os
import fcntl
import asyncore

class AsynStdin( object ):
    def __init__( self, map = asyncore.socket_map ):
        fcntl.fcntl( sys.stdin, fcntl.F_SETFL, os.O_NONBLOCK )
        map[sys.stdin.fileno()] = self

    def readable( self ):
        return True

    def writable( self ):
        return False

    def handle_read_event( self ):
        pass # Do something...

Python 2.6, 2.7 にバックポートされている 3.x の機能を有効化する

from __future__ import (
    division,
    absolute_import,
    print_function,
    unicode_literals,
)
from future_builtins import *

戻る

y.fujii <y-fujii at mimosa-pudica.net>