バッククォートでrepr

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


Python2 だけですが、バッククォートは repr 関数と同じ意味になります。

>>> e = Exception('foo')
>>> repr(e)
"Exception('foo',)"
>>> `e`
"Exception('foo',)"

ただし、Python2 だけの構文であることからも分かるように、この機能は非推奨です*1
これは、『間違えようのない1つの方法があるのがいい』という Zen の教えと、バッククォートは一部のキーボードでは打ちにくいのと、バッククォートだと何をしているのかが分かりにくいという理由でそうなったようです。


ということでちゃんと repr 関数を使いましょう。


.

*1:list comprehension - What do backticks mean to the python interpreter: `num` - Stack Overflow によると、バッククォートを使ったほうが直接命令に落ちて速いらしいですが

リストやタプルの unpack

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


Python のリストやタプルは、展開した上で代入といったことができます。

>>> a, (b, c), d = [(1, 2), (3, 4), (5, 6)]
>>> print a
(1, 2)
>>> print b
3
>>> print c
4
>>> print d
(5, 6)

更に、関数定義時に展開して代入するという指定もできます。

>>> def f((a, b), (c, d)):
...     print a, b, c, d
>>> f((1, 2), (3, 4))
1 2 3 4


Python3 なら、リストやタプルの一部だけ取ってくることも可能です。

>>> first, second, *rest, last = (1, 2, 3, 4, 5, 6, 7, 8)
>>> print first
1
>>> print second
2
>>> print rest
[3, 4, 5, 6, 7]
>>> print last
8


.

文字列のフォーマット出力

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


Python のフォーマット出力は何種類かあります。

>>> print "The %s is %d." % ('answer', 42)
The answer is 42.

タプルによるフォーマット出力です。名前が指定できず、型指定が必要です。

>>> print "The %(foo)s is %(bar)d." % {'foo': 'answer', 'bar':42}
The answer is 42.

辞書によるフォーマット出力です。名前が指定できますが、型指定は必要です。

>>> print "The {foo} is {bar}.".format(foo='answer', bar=42)
The answer is 42.

format 関数によるフォーマット出力です。名前も指定できるし、型指定も必要ありません。
この関数が一番望ましい方法らしいので、今後はこれを使うようにしましょう。


.

filter, map に指定する関数

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


通常、filter, map の第一引数には関数(callable なオブジェクト)を指定しますが、これは None であっても構いません。
None を指定した場合、lambda x: x と大体同じ意味になります*1

>>> filter(None, [0,1,[],3,None])
[1, 3]
>>> map(None, [1,2])
[1, 2]

暗黙bool値変換 で書いたように、x が 0 や [] や None だった場合は False として扱われるので、そういった要素を除けるときに使えます。


map は、この例だと全く意味が無いように見えるかもしれませんが、例えば以下のように複数のリストを纏める場合に使えます。

>>> map(lambda a,b: (a,b), [1,2,3], [4,5,6])
[(1, 4), (2, 5), (3, 6)]
>>> map(None, [1,2,3], [4,5,6]) # 短く書ける
[(1, 4), (2, 5), (3, 6)]

これは zip とよく似た動作になりますが、zip は短いリストに、map は長いリストに合わせられるという点が異なります。

>>> zip([1,2], [1,2,3,4])
[(1, 1), (2, 2)]
>>> map(None, [1,2], [1,2,3,4])
[(1, 1), (2, 2), (None, 3), (None, 4)]

None になるのが嫌な場合、以下のように書けばデフォルト値を入れることができます。

>>> map(lambda a,b: (a if a is not None else 0,
...                  b if b is not None else 0),
...                 [1,2], [1,2,3,4])
[(1, 1), (2, 2), (0, 3), (0, 4)]

ただし、リストの中に None が含まれていた場合はそれもデフォルト値になってしまいます。

>>> map(lambda a,b: (a if a is not None else 0,
...                  b if b is not None else 0),
...                 [1,2], [1,None,3,4])
[(1, 1), (2, 0), (0, 3), (0, 4)]

こういう場合は itertools.izip_longest を使うのがいいでしょう*2

>>> from itertools import izip_longest
>>> list(izip_longest([1,2], [1,None,3,4], fillvalue=0))
[(1, 1), (2, None), (0, 3), (0, 4)]


.

*1:大体、と付けたのは map の場合は lambda *args: args になるから

*2:Python3 なら zip_longest という名前らしい

内包表記いろいろ

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


まず、list, set, dict は、それらを生成するための専用の構文を持っています。

>>> [1,2,3] # リスト生成
[1, 2, 3]
>>> {1,2,3} # 集合生成
set([1, 2, 3])
>>> {1:1,2:2,3:3} # 辞書生成
{1: 1, 2: 2, 3: 3}


この構文を拡張した形で list, set, dict の内包表記が書けます。

>>> xs = [1,2,3]
>>> [x for x in xs] # リスト内包表記
[1, 2, 3]
>>> {x for x in xs} # 集合内包表記
set([1, 2, 3])
>>> {x:x for x in xs} # 辞書内包表記
{1: 1, 2: 2, 3: 3}


ただし、タプルだけは注意です。

>>> (1,2,3) # タプル生成
(1, 2, 3)
>>> (x for x in xs)  # ジェネレータ式 #doctest: +ELLIPSIS
<generator object <genexpr> at 0x...>

タプルの形式で内包表記を行うと、ジェネレータ式として扱われます。
タプルを内包表記で作りたいなら、

>>> tuple(x for x in xs) # タプル作るならこうする
(1, 2, 3)

このように書くのがいいでしょう。


また、内包表記はネストさせることができます。

>>> [(i,j) for i in range(3) for j in range(i)]
[(1, 0), (2, 0), (2, 1)]

これは以下のように書くのと大体同じです。

>>> for i in range(3):
...     for j in range(i):
...         print (i,j)
(1, 0)
(2, 0)
(2, 1)

当然これらは set や dict、ジェネレータでも使えます。

>>> {(i,j) for i in range(3) for j in range(i)}
set([(2, 0), (1, 0), (2, 1)])
>>> {i:j for i in range(3) for j in range(i)}
{1: 0, 2: 1}
>>> list((i,j) for i in range(3) for j in range(i))
[(1, 0), (2, 0), (2, 1)]


もちろん if と組み合わせることもできます。

>>> [(i, j) for i in range(5) if i%2 == 0 for j in range(i) if j%2 == 0]
[(2, 0), (4, 0), (4, 2)]


うまく活用していきましょう。そしてどこで改行するか悩みましょう。


.

再 raise

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


Python で受けた例外をそのまま上に伝える場合、単に raise と書くだけで伝えられます。

>>> try:        #doctest: +ELLIPSIS
...     raise Exception, 'error'
... except Exception, e:
...     if e.message == 'error':
...         raise
Traceback (most recent call last):
    ...
Exception: error

これは、以下のように書いたのと同じです。

>>> import sys
>>> try:        #doctest: +ELLIPSIS
...     raise Exception, 'error'
... except Exception, e:
...     if e.message == 'error':
...         info = sys.exc_info()
...         raise info[0], info[1], info[2]
Traceback (most recent call last):
    ...
Exception: error

raise の引数はそれぞれ、例外の型を表すオブジェクト、例外オブジェクトに渡す引数、トレースバック情報となります。


単に raise e と思うかもしれませんが、以下のようになってしまいます。

>>> def f3():
...     raise Exception, 'error'
>>> def f2():
...     try:
...         f3()
...     except Exception, e:
...         raise e
>>> def f1():
...     try:
...         f2()
...     except Exception, e:
...         raise e
>>> f1()        #doctest: +ELLIPSIS
Traceback (most recent call last):
  File "....py", line 1254, in __run
    compileflags, 1) in test.globs
  File "<doctest __main__[3]>", line 1, in <module>
    f1()
  File "<doctest __main__[2]>", line 5, in f1
    raise e
Exception: error

実際に例外を投げたのは f3 関数ですが、トレースバック情報を見てみると、f1 関数で例外が投げられたことになっています。


raise を使えば、

>>> def f3():
...     raise Exception, 'error'
... 
>>> def f2():
...     try:
...         f3()
...     except Exception, e:
...         raise
... 
>>> def f1():
...     try:
...         f2()
...     except Exception, e:
...         raise
... 
>>> f1()        #doctest: +ELLIPSIS
Traceback (most recent call last):
  File "....py", line 1254, in __run
    compileflags, 1) in test.globals
  File "<doctest __main__[7]>", line 1, in <module>
    f1()
  File "<doctest __main__[6]>", line 3, in f1
    f2()
  File "<doctest __main__[5]>", line 3, in f2
    f3()
  File "<doctest __main__[4]>", line 2, in f3
    raise Exception, 'error'
Exception: error

このように f3 で例外が投げられたことが分かります。


ということで、ちゃんと raise を使う、あるいはトレースバック情報(sys.exc_info()[2])をちゃんと入れるようにしましょう。


.

for-else, try-else 構文

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


Python の for や try 文では、else 節を含めることができます。

>>> def find(xs, v):
...     for x in xs:
...         if x == v:
...             print 'found'
...             break
...     else:
...         print 'not found'
>>> find([1,2,3], 2)
found
>>> find([1,2,3], 5)
not found

for の場合は、その中で break や return されず、最後までループした場合に else 節が処理されます。

>>> def foo(f):
...     try:
...         f()
...     except Exception, e:
...         print 'except'
...     else:
...         print 'else'
...     finally:
...         print 'finally'
>>> foo(lambda: 0)
else
finally
>>> foo(lambda: 1/0)
except
finally

try の場合は except の逆になります。つまり try 文の中で例外が投げられなかった場合のみ呼ばれます。


.