歩いたら休め

なんでこんな模様をしているのですか?

【Python】はてなブログのOAuth認証でブログを自動投稿するスクリプトを書いた

不動産関連のニュースを自動でスクレイピングで集めていたのですが、 Python2から3に移行する際にOAuth認証に移行しようとして、ずっと面倒でサボっていました。

Rubyでは、既にgemの形で実装している方がいました。記事の内容も素晴らしかったし、コードも読みやすく、勉強させていただきました。

blog.kymmt.com

ちなみにACCESS TOKENやACCESS TOKEN SECRETの取得時にスクリプトを利用させてもらいました。ありがとうございます😭🙏

get_access_tokenコマンドで、「承認を求める操作」のスコープを全部ONにしなければACCESS TOKENを発行できない仕様だったので、 時間のあるときにスコープも指定して認証できるようにするプルリク送ろうと思います。

さて、Pythonでは以下のようなコードになりました。

ひたすら面倒だと噂のOAuth認証requests-oauthlibライブラリと、 最初の取得は先のRubyスクリプトで終わらせてしまったため、あまり勉強にはならなかったかも…。

また、自分も余裕があるときにPythonのライブラリにしたいですね。

はてなブログAtomPub - Hatena Developer Center

多分「スクレイピングした結果をデータベースに入れて、Webサイトを作る」というのがエンジニアのあるべき姿だと思うのですが、 私には今のところそこまでの根気はありません…。

【Python】PyPIに『pychatwork』を公開しました

以前練習で作ったライブラリを、仕事でも後輩のタスクで使うようになった(らしい)ので、PyPIに公開してインストールできるようにしました。

pypi.python.org

登録手順など、こちらのページを本当に助けられました。ありがとうございます。

qiita.com

テストコードやSphinxドキュメント生成もやってみたかったのですが、ちょっと面倒になってしまってやってませんw

コードやひどい面が多いと思いますので、お気づきの点があればプルリク or ご指摘お願いしますm( )m

【Python】データサイエンティストのためのPython開発記事紹介

後輩が数値計算を使う、簡単なバッチ処理Pythonで書き始めました。

一応私もPythonの知識ならそれなりにあるのでいろいろ教えられることは(まだ)あります。 そのための予行練習としていろいろまとめておきます。

正直、自分よりもっと数値計算やプログラミングに関する知識と経験豊富なエンジニアがいれば、 彼ももっといろいろなことができるようになっているんじゃないかと思ってしまい、申し訳なさを感じています。

Pythonの言語のイディオムを覚えよう

まずは、道具であるプログラミング言語を使いこなせるようになりましょう。 おすすめは『Pythonチュートリアル第3版』です。

Pythonチュートリアル 第3版

Pythonチュートリアル 第3版

WEBでも内容が公開されています。

Python チュートリアル — Python 3.5.2 ドキュメント

内容は割と被りますが、『Dive into Python3』も素晴らしい資料です。こちらのほうが若干内容が高度です。

Dive Into Python 3 日本語版

numpyのベクトル演算を使いこなそう

既にRのベクトル演算を使いこなしているので基本的には問題ないと思いますが、 一部のコードでpandasのデータフレームをイテレーターとして回していたり、 もっと効率の良い&読みやすいコードが書けるんじゃないかと思っています。

(レビューする際、ロジックの細かいところまで見れていなくてゴメンナサイ…)

Pythonの数値計算ライブラリ NumPy入門 « Rest Term

関数型言語をひとつ勉強して、mapfilterreduceが使いこなせるようになれば、 ベクトルや行列演算も自然に使いこなせるようになるでしょう…が、けっこう負担が大きいかも…。

パッケージ化しよう

Rの記事ですが、パッケージ化するメリットはこちらの記事にまとめられています。

Rのコードをパッケージの形で管理すると、次のような利点があります。

  1. 他の人へのコードの共有が簡単になる(コマンド1つでインストールされる!)
  2. 実行結果が、環境に依存せず再現する(reproducible)ことが担保しやすくなる
  3. 規約に従うことで、本質的な実装に集中しやすくなる

Rのパッケージは上記 1, 2 の利点を実現するために、「テンプレートやさまざまな規約(convention)」を採用しています。一見きゅうくつに思えるかもしれませんが、こうした実装上の決め事に従うことにより、開発者はファイルの適切な配置に迷う必要がなくなり、本質的な処理の実装に集中しやすくなるのです。

ところが、歴史的な経緯(?)によって標準的なパッケージ管理・開発の方法のやり方の方法の情報が錯綜していて、正直自分にもどういうやり方がいいのかよくわからないです。

(完全に余談ですが、最近Elixirで遊んでいて、hex new xxxというコマンドでプロジェクトの標準的なディレクトリ構成が作られ、そのままスムーズにパッケージ開発できることを知って驚きました。Pythonでもこういう仕組みが欲しいですね。)

ひとまず、以前WACODE(わこうど)夏期講習チュートリアルで使われていたリポジトリを参考にすると良いでしょう。

github.com

qiita.com

単体テストSphinxドキュメントを書こう

パッケージ化する中で、単体テストとドキュメントを書いておくと、運用や引き継ぎが楽になると思います。 また、これらはWEB APIバッチ処理を書く際にも使えます。

Pythonには標準で添付されているunittestライブラリで単体テストが書けます。

adtech-blog.united.jp

テストにもいろいろなレイヤーがあるので、「機械学習のロジックの精度の検証までできるの?」と思うかもしれませんが、それは無理です。 交差検定とか、実データ流してテストするとか、そっちの知見が必要です。

unittestはすごく雑にいうと、値のif文での条件の網羅のし忘れ等のポカミスを防げます。 また、「どんな入力が想定されているのか」という仕様書のような役割もしてくれます。

もちろん、人が書くものなので、どんな入力データ(例えば境界値)を入れればいいかといった、基本的なソフトウェアテストの知識は必要です。

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

「そんなのJupyter notebookで試しているから大丈夫だよ」と思うかもしれませんが、だんだんコードが大きくなるに従ってつらくなるはずです。 また、今後運用していく中で、言語やライブラリのバージョンを上げた際に、異常を見つけるのにも役立ちます。

また、ドキュメント生成にはSphinxというツールを使うのが一般的です。

qiita.com

sphinx-users.jp

実行速度の早い言語も習得しよう

なんとなく感じていると思いますが、Pythonはそれほど早い言語ではありません。

コアの計算ロジック部分だけCやC++で書くという選択肢もあると思います。今からやるならJuliaもいいかもしれません。

…が、このあたりは私の経験不足のせいでアドバイスできるほどの知識は持っていません。

開発側の知識も持っておこう

もはやPython関係ないですが、今作っているような最適化のアルゴリズムを、 巨大なサービスに埋め込むのは大変な苦労がかかることは想像できると思います。

そんなとき、簡単にWeb APIや小さいプログラムに分けて、簡単に差し替えできるように作るべきです。 また、おそらく分析結果のロジックを、簡単にデプロイできるようなサービスやソフトウェアも増えてくると思います。 (既にいいものがあるかもしれません。) 「マイクロサービス」とか「サーバーレス」とかその辺のワードのニュースを追っておくと良いかもしれません。

そういうものが出てきた際に困らないように、ニュースを追っておきましょう。 多分、フロントエンドより、バックエンドやミドルウェアの知識が必要になると思います。

全て自分ひとりでできるようになる必要はないと思います。どちらかというと開発者側とのコミュニケーションのために知っておいたほうがいいです。

【Python】pip installで突如UnicodeDecodeErrorが出始めたので対処した

Github上にアップした自作のPythonモジュールをインストールしようとしたところ、UnicodeDecodeErrorが出てしまいました。

$ pip install --upgrade git+https://github.com/takeshi0406/twlist_to_urllist
Exception:
Traceback (most recent call last):
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/basecommand.py", line 215, in main
    status = self.run(options, args)
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/commands/install.py", line 272, in run
    with self._build_session(options) as session:
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/basecommand.py", line 72, in _build_session
    insecure_hosts=options.trusted_hosts,
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/download.py", line 329, in __init__
    self.headers["User-Agent"] = user_agent()
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/download.py", line 93, in user_agent
    from pip._vendor import distro
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 1051, in <module>
    _distro = LinuxDistribution()
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 594, in __init__
    if include_lsb else {}
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 922, in _lsb_release_info
    stdout, stderr = stdout.decode('ascii'), stderr.decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 22: ordinal not in range(128)
Traceback (most recent call last):
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/bin/pip", line 11, in <module>
    sys.exit(main())
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/__init__.py", line 233, in main
    return command.main(cmd_args)
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/basecommand.py", line 251, in main
    timeout=min(5, options.timeout)) as session:
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/basecommand.py", line 72, in _build_session
    insecure_hosts=options.trusted_hosts,
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/download.py", line 329, in __init__
    self.headers["User-Agent"] = user_agent()
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/download.py", line 93, in user_agent
    from pip._vendor import distro
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 1051, in <module>
    _distro = LinuxDistribution()
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 594, in __init__
    if include_lsb else {}
  File "/home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages/pip/_vendor/distro.py", line 922, in _lsb_release_info
    stdout, stderr = stdout.decode('ascii'), stderr.decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 22: ordinal not in range(128)

「Anacondaを使っているせいでは?」「サーバーの文字コードの設定がおかしいのでは?」とも疑ったのですが、以前は同じサーバーで動作していたし、そもそも通常のPythonでも同様の現象が置きており、pip自体のバグのようでした。

エラー文に従い、site-packages/pip/_vendor/distro.pyの922行目を

        stdout, stderr = stdout.decode('ascii'), stderr.decode('ascii') 

から

        stdout, stderr = stdout.decode('utf-8'), stderr.decode('utf-8') 

に書き換えたところ、動作するようになりました。(標準出力や標準エラー出力を処理している箇所のようでした。)

どうやら、pipのバージョンを上げた際に動作しなくなっていたようです。

$ pip --version
pip 9.0.0 from /home/takeshi/.pyenv/versions/anaconda3-2.5.0/lib/python3.5/site-packages (python 3.5)

11月8日追記

issueとして報告しようと確認したところ、既にpip9.0.1のバージョンでこの問題は解決されていました。

github.com

【Python】Rubyの配列やハッシュのメソッドをPythonで再現する

友だちが「Rubyはいろいろなメソッドがあって柔軟だから、 そっちに慣れちゃうとPython書くときちょっと困るんだよね」と言っていました。

たしかに、Rubyは「配列やハッシュに対してこんな機能があればいいな」と感じたとき、 そのデータ型のリファレンスを調べれば大抵見つかります。

しかし、Pythonでもいろいろな便利な関数を使って同等の機能を実現することができます。 ただし、Rubyに比べてオブジェクト指向っぽくないところがあり、 オブジェクトからメソッドを呼び出すのではなく関数やラムダ式を利用する必要あるので、 最初は少し戸惑うかもしれません。

そこで、「Rubyの便利なメソッドを、Pythonのコードで自然に書くならこうだろう」という記事を まとめてみることにしました。 といってもキリがないので、配列とハッシュのメソッドを中心にします。

Rubyは2.2.2、Pythonは3.4.1を利用します。 また便宜上、この記事の中ではPythonの「リスト」と「辞書」をそれぞれRubyの対応する型である 「配列」「ハッシュ」と呼ぶことにします。

$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
$ python -V
Python 3.4.3

余談ですが、Pythonは「ハッシュテーブル」を「辞書」と呼ぶなど、 プログラマ以外にも直感的に分かりやすい名前をつけたがるようです。 リストと言われてLispにあるような連結リストを想像する方もいるかもしれませんが、 実際は配列なのでご注意ください

既に素晴らしい記事を書いている方がいらっしゃるのでそちらもご覧ください。 車輪の再発明感は拭えませんが、この記事ではもう少し細かいところまでまとめられればと思います。

yoghurt1131.hatenablog.com

each

プリパラアイドルをターミナルに列挙するプログラムを書きたいとします。

Rubyでもforの制御構文は用意されているものの、 実際のプログラミングではeachメソッドを呼び出すほうが好まれているようです。 オブジェクト指向!って感じですね。

# Ruby
idols = %w(laala mirei sophy)
idols.each do |idol|
  puts idol
end

ちなみにRubyの元ネタの純粋オブジェクト指向言語Smalltalkではさらに徹底しており、 条件分岐(if)も真偽値のメソッドとして定義されているという 話を聞いたことがあります。

ハッシュの各要素をループさせることもできます。

# Ruby
idol_group_dict = {
  'sorami smile' => %w(laala mirei sophy),
  'dressing pafe' => %w(shion dorothy leona)
}
idol_group_dict.each do |group_name, members|
  members.each do |idol|
    puts "#{idol} is a member of #{group_name}"
  end
end

Pythonではfor ~ in ~の制御構文を使ってこう書きます。

# Python
idols = ['laala', 'mirei', 'sophy']
for idol in idols:
  print(idol)

ハッシュの各要素をループさせるには、辞書型の.items()メソッドを利用します。 ふつうにfor文に渡すとハッシュのキーしか取り出せないので面食らうかもしれません。

# Python
idol_group_dict = {
  'sorami smile': ['laala', 'mirei', 'sophy'],
  'dressing pafe': ['shion', 'dorothy', 'leona']
}
for group_name, members in idol_group_dict.items():
  for idol in members:
    print('{} is a member of {}'.format(idol, group_name))

ちなみにPython3.6からはf'{idol} is member of {group_name}')という形でも 文字列リテラルができるようになるそうですRubyに比べて冗長だったので嬉しいです。

each_with_index

インデックスも含めてループさせたい場合、Rubyではeach_with_indexを使うと便利です。

# Ruby
school_rules = %w(生徒は自分に自信を持たなければならない)
school_rules.each_with_index do |rule, i|
  puts "パプリカ学園校則第#{i+1}条! #{rule}"
end

インデックスを0以外から始めたい場合は、each.with_indexを使います。 (eachメソッドでEnumerator型に変換した後、with_indexメソッドを呼び出しています。) ちなみにeachだけでなくmap等の他のメソッドとも組み合わせられます

# Ruby
school_rules = %w(生徒は自分に自信を持たなければならない)
school_rules.each.with_index(1) do |rule, i|
  puts "パプリカ学園校則第#{i}条! #{rule}"
end

Pythonではenumerateという関数を利用します。

# Python
school_rules = ['生徒は自分に自信を持たなければならない']
for i, rule in enumerate(school_rules):
  print("パプリカ学園校則第{}条! {}!".format(i+1, rule))

インデックスを1から始めたい場合は、enumerateの引数に指定してあげるだけです。

# Python
school_rules = ['生徒は自分に自信を持たなければならない']
for i, rule in enumerate(school_rules, 1):
  print("パプリカ学園校則第{}条! {}!".format(i, rule))

each_cons

子どものために「おかあさんといっしょ」のエンディングテーマを出力したいお父さんプログラマーも多いことでしょう。 (今でもこの歌なんでしょうか?)

そんなとき、each_consを使うと自然に書けます。

# Ruby
song_with_mam = %w(クジラ ラッコ コアラ ライオン 女の子 男の子)
song_with_mam.each_cons(2) do |x, y|
  puts "#{x} になりたい #{y}"
end
# => クジラになりたいラッコ♪
# => ラッコになりたいコアラ♪
# ...

このような場合にはPythonではどう書けば自然なのかパッと思いつきません…。zip関数を使って

# Python
song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子']
for x, y in zip(song_with_mam, song_with_mam[1:]):
  print('{}になりたい{}♪'.format(x, y))

とか2つループを回して

# Python
song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子']
for i, x in enumerate(song_with_mam):
  for j, y in enumerate(song_with_mam):
    if j - i == 1:
      print('{}になりたい{}♪'.format(x, y))

とか、一つ前の値を残して

# Python
song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子']
prev = None
for x in song_with_mam:
  if x is not None:
    print('{}になりたい{}♪'.format(prev, x))
  prev = x

とかになるんでしょうか?

と思ったのですが、Pythonイテレータを扱う標準ライブラリであるitertoolsを使うのが自然かもしれません。 Itertools レシピのサンプルコードを使うとこのようになります。

# Python
from itertools import tee

def pairwise(iterable):
  a, b = tee(iterable)
  next(b, None)
  return zip(a, b)

song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子']
for x, y in pairwise(song_with_mam):
  print('{}になりたい{}♪'.format(x, y))

要素が3つ以上の場合のすぐに拡張もできそうです。ただ、複数個ずつ要素を取り出すだけなのに、大げさなコードになってしまった感はあります。

ところで、メソッド名のeach_consconsってLispのcons)が由来なんしょうか?

each_slice

既に素晴らしい記事を書いている方がいました。

http://blog.livedoor.jp/dankogai/archives/51838970.html

このエントリを見て、PythonRubyのeach_sliceを書くとしたらどうなるだろうと思ってやってみた。

こちらもitertoolsを使うのが自然のようですね。 Itertools レシピにあるコードがこちらです。 (上の記事ではPython2系の時代なのでizip_longestという関数名になっています。)

# Python
from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

length (size)

配列やハッシュ、そして文字列等の長さを調べるメソッドです。

# Ruby
triangle = %(kanon pinon junon)
puts "Triangle is a group of #{triangle.length} idols."

Pythonでは、メソッドではなくlenという関数を使います。Pythonではある程度一般的な操作 (RubyではEnumerableのメソッドにあるようなもの)は 関数や構文として用意されていることが多いです。

triangle = ['kanon', 'pinon', 'junon']
print('Triangle is a group of {} idols.'.format(len(triangle)))

sort, sort_by, sort!, sort_by!

配列の中身をソートする場合、 Rubyではsortを呼び出すか、sort_byのブロックにソート基準を渡すことが多いと思います。

(個人的に、sortメソッドでブロックを呼び出すとき、宇宙船演算子(<=>)を定義するか、 戻り値に-1 or 0 or 1を返すブロックを作る必要があって、頭の中がごちゃごちゃしてしまうことが多いです…。)

# Ruby
p [3, 0, 1].sort
# => [0, 1, 3]

# 名前の長さでソートする
names = %w(laala fuwari dore shio)
p names.sort_by { |n| n.size }
# => ["shio", "dore", "laala", "fuwari"]
p names.sort_by(&:size) # 簡略化した書き方

余談ですが、sort_bysortメソッドは安定ソートでない(比較結果が同じ要素を元の順序通りに並ばないことがある) そうなので注意が必要です。

また、既にご存じだと思いますが、sort!sort_by!オブジェクト自身を変化させる破壊的メソッドです。 コピーを作らないため、破壊的メソッドのほうがパフォーマンスが良い場合が多いようです。

names = %w(laala fuwari dore shio)
names.sort_by! { |n| n.size }
p names
# => ["shio", "dore", "laala", "fuwari"]

Pythonでは非破壊操作はsorted関数を、 破壊的操作は配列(リスト型)の.sortメソッドを使います。 ちなみに、これらのソートは安定ソートだそうです。

# Python
print(sorted([3, 0, 1]))
# => [0, 1, 3]

# 名前の長さでソートする
names = ['laala', 'fuwari', 'dore', 'shio']
print(sorted(names, key=len))
# => ["shio", "dore", "laala", "fuwari"]

破壊的操作の場合のコードです。

# Python
names = ['laala', 'fuwari', 'dore', 'shio']
names.sort(key=len)
print(names)
# => ["shio", "dore", "laala", "fuwari"]

ところで、Rubyのブロックという強力な機能が使えない代わりに、 Pythonでは高階関数で(比較基準となる関数を引数に与えて)機能を実現していることが多いです。

たまにPythonは「配列.sizeじゃなくてlen(配列)なのはオブジェクト指向言語として一貫性がないよね?」 とツッコまれていますが、メソッドとして定義してしまうとnames.sort(key=lambda x: x.size)のような冗長な書き方になってしまうから、 という理由もあるように思います。

また、Rubysortメソッドにブロックを渡したときのように、2つずつの要素を取り出して比較したい場合もあるかもしれません。

Python2.7では、sorted関数/.sortメソッドにcmpという引数があり、 組み込み関数に宇宙船演算子(<=>)と同じ挙動のcmp関数が用意されていたのですが、 Python3.5では削除されています。

Python3.5で2.7のcmpのような挙動(Rubysortメソッド+ブロックのような挙動)が欲しい場合、 公式ドキュメントによると、 標準ライブラリのfunctoolsの中にcmp_to_keyがあり、これを使って変換した関数をkey引数に渡せばいいようです。

旧式の cmp 関数を key 関数に変換するには functools.cmp_to_key() を使用してください。

min, min_by, max, max_by

これもsortの場合と似ています。

# Ruby
# 一番大きな数字を探す
tension = [1, 2, 3, 4, 5]
p tension.max
# => 5

idols = [
  {name: 'mirei', gobi: 'puri'},
  {name: 'ajimi', gobi: 'davinci'},
  {name: 'cosmo', gobi: 'cosmic'}
]

# 語尾が一番長いキャラクターを探す
p idols.max_by { |x| x[:gobi].size }
# => {:name=>"ajimi", :gobi=>"davinci"}

Pythonの場合、maxの引数keyに関数を渡すこととRubymix_byのような挙動になります。

# Python
# 一番大きな数字を探す
tension = [1, 2, 3, 4, 5]
print(max(tension))

idols = [
  {'name': 'mirei', 'gobi': 'puri'},
  {'name': 'ajimi', 'gobi': 'davinci'},
  {'name': 'cosmo', 'gobi': 'cosmic'}
]

# 語尾が一番長いキャラクターを探す
print(max(idols, key=lambda x: len(x['gobi'])))
# => {'name': 'ajimi', 'gobi': 'davinci'}

reverse, reverse!

これもソートの場合とよく似ており、reversed関数や.reverseメソッドを利用します。

ただし、reversed関数の戻り値は配列(list)ではなくlistreverseiteratorというイテレーターオブジェクトが返ってくるので注意しましょう。

# Python
print(reversed([1, 2, 3]))
# => <list_reverseiterator object at 0x103c03320>

# list型に変換しましょう
print(list(reversed([1, 2, 3])))

公式ドキュメント(http://docs.python.jp/3.5/library/functions.html#reversed)には以下のような記述があります。

要素を逆順に取り出すイテレータ (reverse iterator) を返します。 seq は __reverse__() メソッドを持つか、シーケンス型プロトコル (__len__() メソッド、および、 0 以上の整数を引数とする __getitem__() メソッド) をサポートするオブジェクトでなければなりません。

Pythonは、Rubyに比べてカジュアルに遅延評価を行う言語だからかもしれません。

# Python
# long_listは超長い配列
for x in reversed(long_list):
  function(x)

このような場合、reversed関数が配列を返す実装だった場合、reversed(long_list)で一度配列全部をループして操作した後に もう一度for文のループが回ります。 遅延評価するオブジェクトを返すことで、ループが一度で済むようになっています。

正直、reverseではそんなに遅延評価のメリットを感じられませんが、mapfilterでは役立ちます。

inject (reduce), each_with_object, join

Pythonでもreduceという畳み込みの関数が用意されているのですが、 Effective Rubyの『項目19 reduceを使ってコレクションを畳み込む方法を身に付けよう』を読んで その便利さに驚かされたことがあります

Effective Ruby

Effective Ruby

例えば、(以前の記事のそのままの例なのですが)URLの配列(リスト)を、{URL => ページタイトル}というハッシュテーブル(辞書)に変換したいとします。 同時に、ページのURLにgoogleが含まれているものを除外したいとします。

このようなとき、injectの初期値をハッシュにすることで、ハッシュの中に畳み込んでいくことができます。

# Ruby
# ループした結果をハッシュ(デフォルト値では{})のキーに代入していく
def create_url_table(urls)
  urls.inject({}) do |hash, url|
    hash[url] = get_title(url) unless url =~ %r{google}
    hash
  end
end

しかし、私は今ではeach_with_objectを利用してます。

# Ruby
def create_url_table(urls)
  urls.each_with_object({}) do |url, hash|
    hash[url] = get_title(url) unless url =~ %r{google}
  end
end

Pythonでは同様の処理を辞書内包表記を使って書くことができます。

# Python
def create_url_table(urls):
  return {url: get_title(url) for url in urls if 'google' not in url}

もう少し条件が複雑になってくると、ジェネレーターに切り出すようにしています。 『コレクションを畳み込む方法を身に付けよう』で紹介されているような例だと、 内包表記やジェネレーターを使うことが多いです。

# Python
def create_url_table(urls):
  return dict(_url_generator(urls))

def _url_generator(urls):
  for url in urls:
    if 'google' not in url:
      yield (url, get_title(url))

Rubyでは配列の中身の数値の合計値を計算する際にもinjectを使うため、injectが登場する機会は多いです。

# Ruby
[1, 2, 3].inject(:+)

# Ruby2.4から.sumも利用できるようになるそうです
[1, 2, 3].sum

一方で、Pythonは組み込み関数であるsumを利用します。 そのせいか、Python3.*になった際にreduceは組み込み関数でなくなってしまい、 functoolsから呼び出す必要があります。

# Python
sum([1, 2, 3])

同じように、一つの文字列に結合する際には、Rubyでは配列から文字列結合のメソッド.joinを呼び出しますが、

# Ruby
puts %(laala mirei sophy).join(', ')
# => "laala, mirei, sophy"

Pythonでは、文字列型の.joinメソッドに配列を渡すようになっています。 これもlen関数と同じく、オブジェクト指向言語プログラマーが嫌がることの一つです。

# Python
print(', '.join(['laala', 'mirei', 'sophy']))
# => "laala, mirei, sophy"

余談ですが、Rubyinjectreduceの別名のエイリアスが用意されていることについては、 Rubyist Magazine - map と collect、reduce と inject ―― 名前の違いに見る発想の違いの内容が面白かったのでぜひ読んでください。

map (collect), select, reject

配列全体に関数やメソッドを適用するmapと、 条件に当てはまる要素だけを残すselect(と真偽値が逆のバージョンのreject)です。

Rubyの配列操作をPythonで書くと?』の記事にある通りです。 ただし、Python3.*ではmapfilterが遅延評価されるように変更されています。 詳しくは以下の記事を読んでください。

postd.cc

mapfilterなどの公開関数と、ジェネレーター内包表記で機能が被っています。 どちらを使ってもいいのですが、私は内包表記のほうをよく使います。 配列の要素の変換とフィルタリングが同時に行えるし、 括弧によって戻り値をリスト、ジェネレーター、集合(set)を選べるからです。

Rubyには破壊的操作版のmap!select!もあります。 これは関数の中でメソッドチェーンする際に使っています。 それ以外で使ったことは私はほとんど無いです。

# Ruby
# (あんまり例は良くないですが)メソッドチェーンで書く代わりに
def count_group_mamber(idols, group_name)
  idols.filter { |x| x[:group_name] == group_name }.
    map { |x| x[:name] }.
    size
end

# 関数の中では破壊的変更のメソッドで書いています
def count_group_mamber(idols, group_name)
  arr = idols.filter { |x| x[:group_name] == group_name }
  arr.map! { |x| x[:name] }
  arr.size
end

RubyでもPythonと同様に遅延評価を行いたいなら、.lazy.mapのように書きます。 詳しくは『怠惰で短気で傲慢な君に贈るRubyの遅延評価』を読みましょう。

empty?

Rubyには、配列が空かどうかを判定する.empty?というメソッドがあります。 これにより明快なコードが書けるようになります。

例えば、名前の配列(names)を名簿を管理しているDBに問い合わせるような場合、 もしnamesが空ならDBに問い合わせるまでもなく空の配列を返せばいいとします。

# Ruby
def search_ids(names)
  return [] if names.empty?
  # 名簿DBに問合せる処理
  # ...
  ids
end

Pythonでは.empty?のようなメソッドは無いため、空の配列が偽であることを利用して以下のように書くと思います。

def search_ids(names):
  if not names: # もしくは len(names) == 0
    return []
  # 名簿DBに問合せる処理
  # ...
  return ids

Pythonは「あんまり特殊な関数や構文を追加するとわけわかんなくなるから最小限にとどめよう」という 考え方が強いように思います。

一方で、Rubyでは同じメソッドの別名のエイリアスを用意したり、ifunlessが用意されていたり、 文脈によってきちんと使い分けられれば、コードの意図が明快なプログラミングを行いやすいです。

include?

# Ruby
solami_smile = %w(laala mirei sophy)
puts solami_smile.include? 'sophy'
# => True
puts solami_smile.include? 'dorothy'
# => False

Pythonではinという演算子を利用します。

# Python
solami_smile = ['laala', 'mirei', 'sophy']
print('sophy' in solami_smile)
# => true
print('dorothy' in solami_smile)
# => false

また、in演算子は、ある文字が文字列の中に含まれるかや、ハッシュのキーが存在するか(Ruby.hash_key?メソッドの用途)にも使われます。 (Python2.*では辞書型にhas_keyメソッドも存在したのですが、 3系ではinに統一されました。)

# Python
print('la' in 'laala') # => true
print('do' in 'laala') # => False

idol_group_dict = {
  'sorami smile': ['laala', 'mirei', 'sophy'],
  'dressing pafe': ['shion', 'dorothy', 'leona']
}
print('sorami_smile' in idol_group_dict)
# => true

any?, all?

配列の中に〇〇なものがいるかどうか、全部〇〇なのかを調べるとき、any?all?のメソッドは便利です。

例えばプリパラの各チームのメンバーの中に〇〇がいるかどうか確認したいとき、以下のようなコードを書くと思います。 (集合演算を使う方法もあると思いますが)

# Ruby
solami_smile = %w(laala mirei sophy)
solami_dressing = %w(laala mirei sophy shion dorothy)
gaarmageddon = %(aroma mikan gaaruru)
boys = %(meganii leona)

def cute?(pripara_idol)
  true
end

# ソラミスマイルの中にガァルマゲドンのメンバーが1人でもいるか
p solami_smile.any? { |x| gaarmageddon.include?(x) }
# => false

# ソラミドレッシングに男子がいるかどうか
p sorami_dressing.any? { |x| boys.include?(x) }
# => true

# ソラミスマイルにソラミスマイルのメンバーが全員含まれるか
p solami_smile.all? { |x| solami_dressing.include?(x) }
# => true

# ソラミスマイルが全員カワイイかどうか
p solami_smile.all? { |x| cute?(x) }
# => true

Rubyany?メソッドではtrueの値が見つかった時点で(all?false が見つかった時点で)、 それ以上のループを打ち切ります。

Pythonではanyallは関数として用意されています。 真偽値の一覧を返す内包表記と組み合わせて使うことが多いです。 (ここでジェネレーターを使うことで、Rubyと同様にany関数で評価している中でTrueが見つかった時点でループを打ち切ることができます)

# Python
def is_cute(pripara_idol):
  return True

# ソラミスマイルの中にガァルマゲドンのメンバーが1人でもいるか
print(all((x in gaarmageddon) for x in solami_smile))
# => False

# ソラミスマイルが全員カワイイかどうか
print(any(is_cute(x) for x in solami_smile))
# => True

Rubyには他にもone?none?などのメソッドもあります。

uniq, uniq!

割とPythonを扱う際に鬼門なのが値を一意にするメソッドです。

順番を気にしなくていい & 配列の中の変数がイミュータブルなら、一度集合型(set)に変換してしまいましょう。 ただし、変数の順番が保持される必要があります。

※ Python3.6からRubyと同じくハッシュの順番が保持されるようになるらしいので、 集合型でも順番が保持されるようになるかもしれません(単なる期待ですが)。

# Python
nums = [1, 2, 3, 4, 2, 3, 4]

nums_set = set(nums)
print(nums_set)
# => {1, 2, 3, 4}

# 配列に戻す
print(list(nums_set))
# => [1, 2, 3, 4]

順番を保持したい場合は、『Python Tips:リストから重複した要素を削除したい』で同様の議論がされています。 また、Itertools レシピの中にも、もう少し難しい場合のサンプルがあります。

# Python
def f7(seq):
    seen = set()
    seen_add = seen.add
    return [ x for x in seq if x not in seen and not seen_add(x)]

print(f7([1, 2, 3, 4, 2, 3, 4]))

また、sorted関数でインデックスを基にソートするという手もあります。

# Python
nums = [1, 2, 3, 4, 2, 3, 4]
nums = sorted(set(nums), key=nums.index)
# => [1, 2, 3, 4]

ただし、ミュータブルなオブジェクト(例えば配列やハッシュ)は、setの要素に含めることができません。 そのため、ユニークにする際にエラーが出てしまいます。

# Python
idol_groups = [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona'], ['laala', 'mirei', 'sophy']]
set(idol_groups)
# ※以下のようなエラーが出ます
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: unhashable type: 'list'

このような場合にどうすればいいのか調べたところ、 『Pythonのリスト内包表記でRubyのuniqメソッドと同じ事をする』 という記事の中にある例がまあ読める例だと感じました。

# Python
idol_groups = [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona'], ['laala', 'mirei', 'sophy']]
print([x for i, x in enumerate(idol_groups) if i == idol_groups.index(x)])
# => [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona']]

コードの中に頻繁に使うなら、 組み込みのlist型を継承してUniqueListみたいな名前で専用のクラスを作ってしまったほうがいいのかもしれません。

freeze

配列やハッシュに限りませんが、オブジェクトに破壊的変更が加えられないように制限するメソッドです。

Pythonではイミュータブルな配列としてタプルが、 イミュータブルな集合型としてfrozensetがあります。

先程uniqのところで述べた通り、ハッシュのキーや集合型にミュータブルなオブジェクトである配列が指定できません。 そのため、そのような場合に配列を指定する必要があるときにタプルを利用します。

「あれ?じゃあfrozendictは無いの?」と思ったら一度提案されてリジェクトされているようです。 ハッシュのキーにハッシュを指定したい場合(ややこしい)は、.items()メソッドで変換したものをタプルにすると良いと思います。

# Python
profile = {'name': 'mirei', 'gobi': 'puri'}
tuple(profile.items())
# => (('name', 'mirei'), ('gobi', 'puri'))

slice

配列の一部を取得するメソッドです。Pythonにも、スライスのための専用の表記があります。

www.pythonweb.jp

transpose

行列(2次元配列)の転置を行う場合、Rubyには専用のメソッドが用意されています。 Pythonでは

# Python
matrix = [[1, 2, 3], [4, 5, 6]]
print(list(zip(*martix)))
# => [(1, 4), (2, 5), (3, 6)]

ただし、転置行列が必要なレベルの数値計算を行う場合、numpyを利用することが多いと思います。 こちらにはtransposeのメソッドが用意されています

flatten

こちらも、Pythonには簡単に操作できるメソッドは用意されていません。

www.lifewithpython.com

group_by

私が好きなメソッドの一つです。以前書いた記事のまんまですが、 メソッドチェーンでデータベースのテーブル操作みたいなことが簡単にできます。

# Ruby
# スターライト学園のアイドルの名簿 
name_list = [
  {'name' => 'ichigo', 'age' => 16, 'group' => 'soleil'},
  {'name' => 'aoi', 'age' => 17, 'group' => 'soleil'},
  {'name' => 'ran', 'age' => 17, 'group' => 'soleil'},
  {'name' => 'akari', 'age' => 13, 'group' => 'luminas'},
  {'name' => 'sumire', 'age' => 14, 'group' => 'luminas'},
  {'name' => 'hinaki', 'age' => 14, 'group' => 'luminas'},
]

def col_mean(list, colname)
  list.reduce(0){|x, y| x + y[colname]} / list.size.to_f
end

# 各グループの年齢で再集計
p name_list.
    group_by{|raw| raw['group']}.
    map{|k, v| {k => col_mean(v, 'age')}}
# => [{"soleil"=>16.666666666666668}, {"luminas"=>13.666666666666666}]

Pythonでは、itertoolsgroupby関数で同様の機能が使えます。 ただし、公式ドキュメントにあるとおり、

  • 戻り値がハッシュ(辞書)ではなくジェネレーターであること(一度ループさせると中身が消える)
  • key 関数の値が変わるたびに新しいグループが作られること

に注意する必要があります。上のようなSQLっぽい集約のために使うなら、あらかじめsorted関数でソートしておきましょう。

simanman.hatenablog.com

まとめ

他にもRubyのメソッドはたくさんありますが、Rubyに慣れたプログラマーがつまづきやすいところを中心に紹介したつもりです。 (もっと良い方法があれば教えてください!)

Pythonもイケてるよ!」という記事にしたつもりなのですが、each_consuniqなどは思ったより再現が難しく感じました。 普段使っている便利なメソッドを、自力で再現すると勉強になりますね…。

また、Pythonitertoolsにはまだ便利な機能が眠っている気がしています。 一度調べてみたいです。

このモジュールは イテレータ を構築する部品を実装しています。プログラム言語 APL, Haskell, SML からアイデアを得ていますが、 Python に適した形に修正されています。

やっぱりHaskellを一度ガッツリ触るべきなのかな…。型のしっかりした言語もやりたいし。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

【Elixir】ElixirのWebフレームワーク、Phoenixをインストールだけしてみた

Elixirという関数型言語が流行り始めているような雰囲気を感じています。

ErlangVM上で動く並列処理に強い言語で、(詳しく知らないのですが)Prologの影響が強くいまいち使いづらいErlangに対して、 Rubyっぽい見た目で使いやすくした言語と聞いています。

R言語をたまに使っている身としては、dplyrやtidyrと同じくF#のパイプライン演算子が使える言語として気になっていました。 仕事でRubyを書いていても、「ここはパイプを使いたいなあ…でも無理やり実装してもRubyオブジェクト指向の世界観に合わないし」と 悩んでいたのでいろいろ試してみたいです。

この間プログラミングElixirが出て、今読みながらいろいろと遊んでみたいです。 プログラムの書き方だけでなく、ドキュメントの文化や、テストコードの書き方まで紹介されており、 かなり実用的で楽しい本だと感じています。

プログラミングElixir

プログラミングElixir

といっても今日はRuby on RailsライクなフレームワークであるPhoenixをインストールして起動しただけの内容です。 公式ドキュメントや下の記事を参考にしました。

APIサーバーを作りたいだけなので、TrotというSinatraっぽいマイクロフレームワークだ という記事を見つけたので、そちらも気になっています。

qiita.com

ひとまず、AWSUbuntu Server 14.04 LTS (HVM)のインスタンスを立てて入れました。特に詰まることもありませんでした。

Elixirのインストール

Elixirの公式ドキュメントにある通りにインストールできます。

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang
sudo apt-get install elixir

次にhexをインストールします。Elixir, Erlang 向けのパッケージ管理ツールで、要するにPythonのpipやRubyのGemのようなものらしいです。

mix local.hex

PostgreSqlのインストール

「The PostgreSQL wiki has installation guides」 という記述があったため、 そのリンク先にあったOfficial Ubuntu Community Wikiに従ってインストールしました。

といってもapt-get installしただけです。

sudo apt-get install postgresql postgresql-contrib

Phoenixのデフォルトではuser, passwordともにpostgresに接続するよう設定されているため、 そのように設定します。

当たり前ですが、デフォルト設定は変えたほうがいいですね。

sudo service postgresql start
sudo -i -u postgres
psql

localのpostgresに接続したら、パスワードをpostgresに設定します。

ALTER ROLE postgres LOGIN PASSWORD 'postgres';
¥q

node.jsのインストール

「Node is an optional dependency. 」と書いていたのですが、一応インストールしました。

sudo apt-get install nodejs npm nodejs-legacy
sudo npm install

Phoenixプロジェクトを作成して立ち上げる

mix phoenix.new hello_phoenix
cd hello_phoenix

立ち上げます。

mix ecto.create
mix phoenix.server

どうやらRuby on Railsのように、コマンドでDBを作成したりいろいろと操作できるようです。

ここまで来て気づいたのですが、私はプライベートなサブネットに作ってしまったので、 ブラウザで接続できません。

curllocalhostを叩いたものをgrepして、"Welcome to Phoenix!"と表示されているのか確認します。

curl http://localhost:4000 | grep "Welcome to Phoenix!"

表示結果です。

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1938  100  1938    0     0   170k      0 --:--:-- --:--:-- --:--:--  172k
  <h2>Welcome to Phoenix!</h2>

大丈夫そうですね。

【Ruby】open-uriでhttp=>httpsのリダイレクトを行う

Rubyスクレイピングしていたところ、PCサイトからスマホサイトにリダイレクトするところで次のようなエラーが出てしまいました。 どうやらRubyのopen-uriではhttpからhttpsへのリダイレクトが禁止されているようです。

kiito.hatenablog.com

open-uri.rb:224:in `open_loop': redirection forbidden: http://***.jp/***/ -> https://***/***/ (RuntimeError)

同じところで詰まっている人も多いらしく、リダイレクトを許可するためのgemも用意されていました。

qiita.com

github.com

ところが、いちいちgemを入れるのもアレな感じがしたので、他のやり方が無いか探してみました。 すると、「リダイレクトしない設定にするとOpenURI::HTTPRedirectエラーが発生するから、それをキャッチしてuriを取り出す方法もある」というのがStack Overflowに書いてました。

stackoverflow.com

先程のコードを直すと、こんな感じになるでしょうか?

def _fetch_url(url, user_agent, count=0)
  open(url, 'User-Agent' => user_agent, redirect: false)
rescue OpenURI::HTTPRedirect => e
  raise e if count >= MAX_RETRY
  _fetch_url(e.uri, user_agent, count+1)
rescue => e
  raise e if count >= MAX_RETRY
  sleep(SLEEP_TIME)
  _fetch_url(url, user_agent, count+1)
end

ただし、実際にはリダイレクト時にCookieを使う必要のある箇所もあり、 まだ未対応ですが、そこに対応するときにコードが煩雑になりそうだったのでおとなしくopen_uri_redirectionsを使わせてもらいました。 (前回訪問したページに合わせてコンテンツを出し分けているようです)

そもそもopen-uriではなく、スクレイピング向けのもっと高機能なライブラリを使うべきなのかもしれません。