リストのappendメソッドはNoneを返す

リスト(数列)の差分のリストがほしかったんだけど、はまったのでメモ。
最初、↓こう書いた。

def difference(lis):
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
    d[0].pop(0)
    return d[0]

a = range(10)
diff = difference(a)
print diff

2行目のx[0].append(y-x[1])で差分を蓄積していってそれを返している(3行目は余分な初期値との差分があるからそれを取り除いている)。
ところが実行するこうだ。

^o^ > python difference.py
Traceback (most recent call last):
  File "difference.py", line 9, in 
    diff = difference(a)
  File "difference.py", line 3, in difference
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
  File "difference.py", line 3, in 
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
AttributeError: 'NoneType' object has no attribute 'append'

‘NoneType’のオブジェクトは’append’なんて属性持ってないと。しばらく悩んだけど、原因はタイトルのとおり。
1回目のappendを呼び出したところでNoneが返ってくるので、2回目の呼び出しでは失敗する。

>>> a = range(5)
>>> a
[0, 1, 2, 3, 4]
>>> type(a.append(5))
<type 'NoneType'>

結局、次のように書き換えたらうまくいったけど、関数がひとつ増えてしまった。

def difference2(lis):
    d = reduce(lambda x, y: (add_list(x[0], y-x[1]), y), lis, ([],0))
    d[0].pop(0)
    return d[0]

def add_list(lis, x):
    lis.append(x)
    return lis

a = range(10)
diff = difference2(a)
print diff
^o^ > python difference2.py
[1, 1, 1, 1, 1, 1, 1, 1, 1]

RubyのArray#pushならselfを返してくれるから素直に書けるんだけどなあ。

「リストのappendメソッドはNoneを返す」への5件のフィードバック

  1. こんばんは
    いつも勉強させていただいています。

    私も同じ事を考えた事がありますがappendがNone を返すのには理由があり、
    http://stackoverflow.com/questions/1682567/why-does-pythons-list-append-evaluate-to-false
    ↑の回答にあるように
    Command–query separation
    の原則に従っているからです。
    破壊的なコマンドに対しては一貫性を持たせて
    プログラマに意識させるような意図があるんだと思います。

    リストの差分を返すならば
    以下のようなやり方もいいかと思います。

    from itertools import starmap
    from operator import sub

    difference = lambda l:list(starmap(sub,zip(l[1:],l)))

    前エントリでも触れられていたzipを使ってます。
    1つずらしたlistをzipして引数にして差分をとる形です。
    importが増えるきらいはありますが
    Haskellに慣れ親しんだ人にはむしろしっくりくるのでは無いでしょうか?

    蛇足ですが、python2系ではzipは前エントリのコメントで触れた
    itertools.izipを使うほうが効率的です。
    python3系ではzipは2系のizipと同様の動作をします。

    1. いろいろ教えてくださってありがとうございます。
      なるほど、1つずらしたリストとzipを使って差分を取るんですか。勉強になります。

  2. 更に蛇足ですがリスト内包表記を使う方がより
    直観的でpythonicかも知れないです。

    >>> difference = lambda l:[x-y for x,y in zip(l[1:],l)]
    >>> difference(range(10))
    [1, 1, 1, 1, 1, 1, 1, 1, 1]

    インポートも要りませんし、より短いです。
    golfがしたいわけでは無いでしょうが ;)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください