イースターエッグ

この記事は Python Tips Advent Calendar 2012 25日目の記事です。


最後は、クリスマスとキリストで繋がった、イースターエッグについてです。
Python に隠された卵を探して楽しみましょう。

The Zen of Python

import this をすることで The Zen of Python がいつでも表示できます。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

しかしこの項目の多さ、複雑だし、詰め込みすぎだし、読み易くないしで、The Zen of Python のテキストは The Zen of Python を意識せずに書かれている気がする*1

空白でのインデントをやめる

from __future__ import braces をすることで、空白でのインデント制御を C のようなブレースのスタイルにできるらしいです。
ということでやってみると…。

>>> from __future__ import braces #doctest: +ELLIPSIS
Traceback (most recent call last):
    ...
SyntaxError: not a chance ...

not a chance(絶対にあり得ないよ!)というエラーが出ます。
ブレースによる制御が標準に入ることは無さそうです*2

import __hello__

>>> import __hello__
Hello world...

なぜか Hello world... という文字列が出力されます。
あと __phello__ を使っても同じです。

>>> import __phello__
Hello world...
>>> from __phello__ import spam
Hello world...

これは C 言語などから Python のモジュールを import するときに、テストのために使ったりするようです。

antigravity

>>> import antigravity

と書くと、ブラウザが開き、このページに飛ばされます。
こんな感じのことを言ってるはず。

A「Python!」
B「飛んでる!どうやって!?」
A「昨日の夜、Python を勉強したんだ。とてもシンプルだったよ。
  Hello World を出力するなら単に print "Hello, World!" と書くだけでいいんだ」
B「動的型付け?空白インデント?よく分からないよ…。」
A「こっちにおいでよ!プログラミングがまた楽しくなるよ!ここからは世界全体が見渡せるよ!」
B「でも君はどうやって飛んだんだい?」
A「import antigravity って打っただけだよ」
B「それだけ?」
A「……あと、Python と比較するために薬棚の中身を全部飲んでみたけど……。
  でもこれは Python だよ!多分!」


.

*1:Python コードじゃないからいいのかもしれないけど

*2:詳細は (訂正)Pythonの__future__ - 西尾泰和のはてなダイアリー を参照

正規表現あれこれ

この記事は Python Tips Advent Calendar 2012 24日目の記事です。


Python正規表現には、便利な機能がいくつかあります。ということで、いくつか見ていきましょう。

コメント機能

>>> import re
>>> re.compile(r'''
...     ^              # start of a line
...     \[font         # the font tag
...     (?:=(?P<size>  # optional [font=+size]
...     [-+][0-9]{1,2} # size specification
...     ))?
...     \]             # end of tag
...     (.*?)          # text between the tags
...     \[/font\]      # end of the tag
... ''', re.VERBOSE) #doctest: +ELLIPSIS
<_sre.SRE_Pattern object at 0x...>

このように VERBOSE フラグを付けるとコメントが付けらます。
VERBOSE フラグは、空白と # 以降の文字列を無視するようになります。もし空白や # を使いたい場合は \# のようにエスケープする必要があります。

デバッグモード

>>> import re
>>> re.compile(r'^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]', re.DEBUG) #doctest: +ELLIPSIS
at at_beginning
literal 91
literal 102
literal 111
literal 110
literal 116
max_repeat 0 1
  subpattern None
    literal 61
    subpattern 1
      in
        literal 45
        literal 43
      max_repeat 1 2
        in
          range (48, 57)
literal 93
subpattern 2
  min_repeat 0 65535
    any None
in
  literal 47
  literal 102
  literal 111
  literal 110
  literal 116
<_sre.SRE_Pattern object at 0x...>

このように、どんな状態機械になったのかが標準出力に出力されるので、デバッグが容易になります。

置換時に関数を渡す

sub 関数を使って置換する場合、通常は文字列を指定しますが、関数を指定することもできます。

>>> import re
>>> re.sub('ab|bb', \
...        lambda m: 'xxx' if m.group(0) == 'ab' else 'yyy', \
...        'aaabbb')
'aaxxxyyy'

このように関数を渡すと、文字列がマッチするたびに関数が呼ばれます。
引数には MatchObject が渡され、どの文字列に一致したかなどの情報が取れます。

複数の文字列置換

sub 関数は、例えば複数の文字列を置換する場合に役立ちます。
例えば html のエスケープなどで普通に replace 関数を使った場合、以下の様な問題に遭遇することがあります。

>>> text = '<hoge>&&&</hoge>'
>>> text.replace('<', '&lt;') \
...     .replace('>', '&gt;') \
...     .replace('&', '&amp;')
'&amp;lt;hoge&amp;gt;&amp;&amp;&amp;&amp;lt;/hoge&amp;gt;'

'<' を '&lt;' に置換した後、'&' を '&amp;' に置換しているので '&amp;lt;' になっています。これは意図した動作では無いでしょう。
この場合であれば '&' の置換を先頭にやれば一応動作はしますが、そのような順序に依存した変換はバグが発生しやすくなってしまいます。


これは sub の引数に関数を指定することで簡単に一括で置換できます。

>>> def multiple_replace(text, dic):
...     if len(dic) == 0:
...         return text
...     import re
...     return re.sub('|'.join(map(re.escape,  dic)),
...                   lambda m: dic[m.group(0)],
...                   text)
>>> multiple_replace('<hoge>&&&</hoge>', {
...     '<': '&lt;',
...     '>': '&gt;',
...     '&': '&amp;' })
'&lt;hoge&gt;&amp;&amp;&amp;&lt;/hoge&gt;'

それぞれの置換対象の文字列を '|' で繋いでどれかにマッチさせるようにして、そのマッチした文字列からどの文字列に置換したいかを指定しています。


Python正規表現はちゃんと使うとかなり便利になるので、一度は re のドキュメントを全部読んでおいた方がいいでしょう。


.

シグネチャ確認

この記事は Python Tips Advent Calendar 2012 23日目の記事です。


dir 関数で、そのオブジェクトが持っているメソッドの一覧を出力できます。

>>> dir("foo")    #doctest: +ELLIPSIS
['__add__', '__class__', ..., 'upper', 'zfill']

また、help 関数を使うことで、その関数やクラスの説明を見ることができます。

>>> help("foo".upper)
Help on built-in function upper:

upper(...)
    S.upper() -> string

    Return a copy of the string S converted to uppercase.

これは単に docstring を表示しているだけなので、以下の様に自作の関数についても説明を見ることができます。

>>> def f(v):
...   '''function f
... 
...   v - value
...   '''
...   pass
>>> help(f)
Help on function f in module __main__:

f(v)
    function f
    
    v - value


.

zlib, base64, rot13 エンコーディング

この記事は Python Tips Advent Calendar 2012 22日目の記事です。


Python でデータを圧縮したい時、通常は zlib モジュールを使ったりするかもしれませんが、特に特殊な指定を必要としないのであれば、encode 関数で事足ります。

>>> s = 'Hello World!!'
>>> s.encode('zlib')
'x\x9c\xf3H\xcd\xc9\xc9W\x08\xcf/\xcaIQT\x04\x00 \xa8\x04_'
>>> s.encode('zlib').decode('zlib')
'Hello World!!'

同様に base64 もあるので、単純な利用の場合はこちらを使うといいでしょう。

>>> s.encode('base64')
'SGVsbG8gV29ybGQhIQ==\n'
>>> s.encode('base64').decode('base64')
'Hello World!!'

あとは rot13 というのもあります。
これは a-z と A-Z を 13 個ローテートさせる、単純な暗号化です。アルファベットは 26 文字あるので、2回エンコードすると元に戻ります。

>>> s.encode('rot13')
'Uryyb Jbeyq!!'
>>> s.encode('rot13').encode('rot13')
'Hello World!!'


.

yield の戻り値を受け取る

この記事は Python Tips Advent Calendar 2012 21日目の記事です。


yield の呼び出しには戻り値があります。
普段は None ですが、呼び出し元が send 関数を使った場合、その引数が yield の戻り値として渡されます。

>>> def f(xs):
...     n = None
...     for x in xs:
...         if n is None or n == 0:
...             n = yield x
...         else:
...             n -= 1
>>> it = f([1,2,3,4,5,6,7,8,9])
>>> it.next()
1
>>> it.send(1) # skip 1
3
>>> it.send(4) # skip 4
8

it.send(1) によって n = yield x の n に 1 が渡されます。


.

関数のデフォルト値の初期化タイミング

この記事は Python Tips Advent Calendar 2012 20日目の記事です。


Python の関数にはデフォルト値が付けられます。
このデフォルト値は、その関数を呼び出したタイミングではなく、その関数を定義したタイミングで初期化されます。

>>> def f():
...     print 'called f'
...     return []
>>> def bar(x = f()): # この bar 関数の定義で f() が呼ばれる
...     x.append(1)
...     return x
called f
>>> bar() # この呼び出しのときには f() は呼ばれない
[1]
>>> bar() # 同じデフォルト値を使っているので、前回のリストが残ったままになっている
[1, 1]
>>> bar()
[1, 1, 1]

このようになります。
もし呼び出したタイミングでデフォルト値を決めたいなら、その引数に通常入ってこない定数と比較するのが一般的なようです。

>>> def bar(x = None):
...     x = x or f() # x が False として評価できる値だったら f() を代入する
...     x.append(1)
...     return x
>>> bar()
called f
[1]
>>> bar()
called f
[1]


.