歩いたら休め

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

【Ruby】HTMLをYAMLに変換して他サイトの改修をチェックする (ver2)

スクレイピングで他サイトの改修をチェックして自社サイトの参考にしよう」という上司の無茶振り要望に応えてあげようキャンペーン第二弾です。

kiito.hatenablog.com

前回のコードを回して、一応改修箇所は検知できていたようですが、正直全然使い物になるレベルではありませんでした。 主に、以下のような変化があった場合にyamlの見た目上の変更箇所が大きく出てしまうことが原因のようでした。

  1. hrefやsrcなど、リンク先のちょっとした変化(css等のキャッシュ用パラメータ等)
  2. (特に商品一覧ページで)商品数の変化すると、<li>タグの数が代わった場合

というわけで、前回のコードを書き直してみました。

  1. htmlの要素のnode_nameattributesが同じだった場合に一つにまとめ、最初の要素を取るようにした
  2. 一応要素の変化も検知できるように、count:というパラメータを導入した
  3. href, src, altタグを無視するようにした

また、Pythonでは辞書(ハッシュテーブル)で順番が保存されず何かと不便なため、Rubyで書き直しました。 Python3.6からは辞書の挿入順が保存されるらしいので、もうちょっと後だったらPythonで書いてたかもしれません。

$ bundle exec ruby honenuki.rb --url='http://google.com' --user-agent='hogehoge-bot' | head   
---
:name: html
:attributes:
  :itemscope: ''
  :itemtype: http://schema.org/WebPage
  :lang: ja
:children:
- :name: head
  :count: 1
  :children:

利用すると↑こんなのが標準出力されます。

肝心のコードはGistにアップしています。

【Python】Selenium + PhantomJSでPythonからブラウザ画面のスクリーンショットを撮る

WEBサイトの改修を検知するプログラムがそれなりにうまくいきそうなのですが、改修があったときにyamlの文字列を見てもピンと来ません。

kiito.hatenablog.com

というわけで、PythonからSeleniumを介してPhantomJSを動作させ、 スクリーンショットを撮ることにしました。

PhantomJS でログインが必要なページでも自由自在にスクレイピング - 凹みTips

PhantomJS はヘッドレスな(ブラウザ画面のない)QtWebKit ベースのブラウザで、JavaScriptAPI を通じて、そのブラウザを自由自在にあやつることが出来ます。使用シーンとしては、Jenkins などの CI ツールとの組み合わせによる Web ページの GUI の自動テストや、Web ページのスクリーンキャプチャ、スクレイピングなどが挙げられます。

実行環境

botとかクローラー動かすのに使っているさくらVPSで試してみました。

$ cat /etc/redhat-release
CentOS release 6.8 (Final)
$ arch
x86_64

node.jsのインストール

qiita.com

sudo yum install epel-release
sudo yum install nodejs npm --enablerepo=epel
# sudo yum install gcc gcc-c++ # 元々インストールしていたので入れなかった

PhantomJSのインストール

npm -g install phantomjs

Pythonselenium webdriverをインストールする

元々pvenvを使って、Python3.5(Anaconda2.5)を入れていたのでインストール手順は端折ります。

$ python -V
Python 3.5.1 :: Anaconda 2.5.0 (64-bit)

Seleniumをインストールします。

# ユーザー権限のpyenv環境上なのでsudoいらない
pip install selenium

Pythonのコードの実行

stackoverflowの記事を参考に、 スクリーンショットを撮るプログラムを書きました。

from selenium import webdriver

driver = webdriver.PhantomJS() # パスは特に指定する必要はありませんでした
driver.set_window_size(1024, 768)
driver.get('https://google.com/')
driver.save_screenshot('screen.png')
driver.close()

以下のファイルが保存できました。これは問題なく使えそうですね。

f:id:takeshi0406:20160929233829p:plain

【R】weblioの住宅用語辞典をスクレイピングして住宅関連の単語のcsvを作る

自然言語処理をやっている人はよく辞書の整備が大変だと言います。

そんな話をしていたところ、「自分が詳しくない分野の言葉を知るときはweblio辞書が便利だよ」という知見を教えてもらいました。

www.weblio.jp

しかし、私は怠惰なプログラマーなので、スクレイピングでデータを取得しました。 試しに、建築・不動産の辞書スクレイピングで引っ張ってくることにしました。

PythonRubyスクレイピングするのは飽きたのでRでコードを書きました。 問題になった(工夫した)のは以下の点です。

  • 英字('a', 'b', 'c'...)、日本語('aa', 'ka', 'sa'...)、数字(0:9)、記号('sign')のURLがある
    • JavaScriptの即時実行式を真似たのはトリッキーすぎるかな…
  • 単語と読みが別々に表示されている
    • 例えば「アウトフレーム工法」「アウトフレームコウホウ」のリンクが2つある
    • リンク先が同じなので、リンク先のデータを取得した後に一意(dplyr::distinct)にする
  • サーバーに負荷をかけすぎないように、3秒ごとにsleepさせています
    • 本当は404の例外が来たときに例外をキャッチして、数秒sleepした後に再実行するようにしたかった
    • 書き捨てのコードなのでやってないです

コードはgistに公開しています。

returnの後にパイプ処理を重ねるのは先輩のコードの癖が移ったものです。お元気でしょうか…。

スクレイピングには、みんな大好きHadley Wickhamrvestライブラリを利用しました。PythonurllibBeautifulSoupRubyopen-uriNokogiri)の処理を一気にやってくれます。

xpath方式などでHTMlの要素を指定できるので、 RubyNokogiriに似た感覚で使うことができました。

github.com

また、高階関数を扱うライブラリpurrrmap_dfがめちゃめちゃ便利ですね。 Map関数(purrr::mapの)戻り値は通常listなのですが、それをデータフレームにしてくれます。

notchained.hatenablog.com

また、細かいところではRには定数(letters)でアルファベットの一覧のベクトルが使えることを初めて知りました。 詳しくはR-Tipsの記事へ。

【PyCon】『メタプログラミングPython』を復習して言語の理解を深めよう

PythonのカンファレンスであるPyConJP2016に一般参加してきました。

様々な発表や催しがあったのですが、特に素晴らしかったのが@tell_kさんの『メタプログラミングPython』という発表です。その名の通りオライリーの「メタプログラミングRuby」の内容に沿って、Pythonについて同様の内容を解説するという内容です。

togetter.com

まず、Pythonのオブジェクトのクラスの継承関係や、+演算子__add__メソッドの糖衣構文で、自作のクラスでも自由に演算子を設定できる(また、右から左に足し算する__radd__というメソッドがあることは初めて知りました)など、なんとなく知っていたことが素晴らしく整理されていました 。

Pythonのコードの文字列を、astモジュールで抽象構文木に変換し、printpprintに変換して実行するというLispのマクロみたいなことを始めたときは脳汁が出そうになりました。こんなことPythonでもできたんですね。

ただし、密度の濃い発表で後半は全く付いていけなかったし、「既に以前のPyConで素晴らしい発表があったので〜」と詳しい解説の無かった箇所もあったので、 今週〜来週あたりで、発表資料や映像をもう一度見返したいです。

メタプログラミングというと、「黒魔術だ」「必要性がなければ使わないほうがいい」と思って敬遠しがちですが、私自身『メタプログラミングRuby』を読んだ後に、Rubyの(継承関係を理解したことで)Mix-inや(Procを理解したことで)ブロックなど、(基本的な機能ばかりなのですが)納得して使えるようになりました。

端的に言うと、「バルス」を知ることで「リーテ・ラトバリタ・ウルス・アリアロス・バル・ネトリール」を使いこなせるようになった感じです。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

同様の経験がPythonでもできることに感謝しています。

【Python】他サイトの改修頻度をチェックするためにHTMLをYAML(っぽい何か)に変換するためのコマンドラインツールを書きました

上司から「サイトがマメに更新されてると、Google検索エンジンからの評価が上がるらしいんだけどさぁ、 他サイトの更新頻度をチェックしたり簡単に比較する方法ない?」というふんわりした話題がありました。

単純に考えると、サイトのHTMLを定期的にスクレイピングしてdiffを取ればいけそうですが、 商品の入れ替え等で細かい文言が変わってしまうことは容易に考えられます。

そこで、HTML(DOM)の構造のみを取り出して、比較的人が見やすいデータ構造であるyamlに変換することにしました。 調べてみると、既にRubyで似たようなコードを実装した方がいたようです。

uasi.hatenablog.com

これをそのまま使わせて頂いてもよかったのですが、一度Pythonコマンドラインツールを作ってみたかったのと、 USER-AGENTくらいは指定したかったので再実装しました。

うわ!_stdout_yamlの中がザ・糞実装みたいな感じですね!

クラスメソッドさんの記事にあったClickというライブラリが簡単そうだったのでそちらを利用しました。 デコレータをちゃんと使ったのは初めてかもしれません…。

dev.classmethod.jp

# クックパッドのトップページをテキストに書き出す
./honenuki.py --url='http://cookpad.com/' > output

結果はこんな感じです。一応yamlとして読み込めるようにはしたつもりですが、diff取るためだけに出力しているので努力はしてません。

- :name: 'html'
- :children:
  - :name: 'head'
  - :children:
    - :name: 'meta'
      :attributes:
        :charset: 'utf-8'
    - :name: 'meta'
      :attributes:
        :content: 'IE=edge'
        :http-equiv: 'X-UA-Compatible'
    - :name: 'script'
    - :name: 'link'
      :attributes:
        :media: 'handheld'
        :rel:
          - alternate
        :type: 'text/html'
        :href: 'http://m.cookpad.com/'
    - :name: 'link'

あとはgit管理して簡単にdiffを見れるようにすればいいだけですが、実際にうまくいくかどうかはやってみないとわからないですね…。

【Ruby】PythonプログラマーがRubyを触って感じたこと

Pythonプログラマーというか、元々Python(ときどきR、C言語)で数値シミュレーションをしていた学生が、就職してRubyでWeb開発を行うにあたって勉強したことを書き連ねていくだけの記事です。

もし自分と同じような立場の人(これから後輩としてもどんどん増えていくかも!)がいたら、「ここを押さえておけばRubyは問題なく書けるよ」と教えられるように書いておきます。というのも、レビューを行っていた先輩とのプログラミングのスキルとの開きがあり、先輩も私も「どこが分かってないのか説明できない」状態になってしまってお互いに困ってしまった経験があるからです。

RubyPythonはよく似ているのですが、思想や見た目で違う部分が多く、片方を勉強するともう片方の理解も深まります。 たまに2ちゃんねるのオカルト板である「見たことある世界によく似た異世界に迷い込んだ」みたいな感覚で、なかなか面白い経験でした。

Rubyのよかった点

まず、Rubyを使ってみて、「これってイケてるな」「勉強になったな」と思ったことを書いていきます。

1. オブジェクト指向が(Pythonよりも)徹底している

Rubyのプログラミングは、だいたい「オブジェクトからメソッド(+ブロック)を呼び出して、その戻り値を利用する」ということで表現できます。(if文などの制御構文など、若干の例外もありますが)

nums = [1, 2, 3]

# 全ての配列の要素に1を足す
nums.map {|n| n + 1}

# 合計を取る(畳み込み)
nums.inject {|x, y| x + y}
nums.inject(:+) # 省略した記法
nums.sum # Ruby2.4から利用できるようになるそうです

対してPythonでは、高階関数やリスト内包表記、合計にはsum関数など、リスト(Rubyの配列に対応するもの)を操作するにしても、色々なことをしているように見えます。

from functools import reduce
nums = [1, 2, 3]

# 全ての配列の要素に1を足す
list(map(lambda n: n + 1, nums)) # map関数を利用した例
[n + 1 for n in nums] # リスト内包表記を使った例

# 合計を取る
sum(nums)
reduce(lambda x, y: x + y, nums) # Rubyと同じく畳み込みを使った例

実はPythonも内部的には__iter__メソッドや__next__メソッドを呼び出しているそうなのですが、 表面上は関数として定義されているように見せているだけです。

これはほぼ書き方の違いだけなのですが、Rubyのほうが「主語がはっきりしていて、何を操作の対象にしているか分かりやすい」という利点があるように思います。 ただし、ブロックをeachから使い始める人が多い分、ラムダ式高階関数を理解しないままmapinjectを使っている人も多いように感じています。

一方、Pythonで数値の合計値にsumという関数を使えばいいというのは、数式の表現に近く、きちんとプログラミングを学んでいない情報系以外の分野の学生には使いやすいように思います。

(完全に余談ですが、Rubyでも2.4からsumメソッドが使えるようになるらしいです。数学のバックグラウンドがある人が、「合計値出すのにinject(:+)ってなんだよ…」って愚痴っていたのを聞いたことあるので個人的には大歓迎です。)

クラスとイテレータ - Dive Into Python 3 日本語版

RubyのMix-inも便利でした。Pythonで同様の機能を実現するには、多重継承を気をつけて使うか、abcというモジュールを使うかするといいらしいです…がPythonであまり大きなコードを書く機会が無かったので、これから身につけたいです。

www.atmarkit.co.jp

2. 破壊的変更が理解しやすい

前項のように、Pythonを利用している間、「オブジェクトの内側からメソッドを呼び出す」という感覚はあまりなく、 「オブジェクトの外側から関数を適用する」という感覚でプログラミングを行っていました。 そのため、「破壊的変更」「ミュータブル/イミュータブル」という概念がいまいちピンと来ていませんでした。

例えば、リスト(配列)をソートする場合、Pythonでは破壊/非破壊で関数とメソッドを使い分ける必要があります。

ソート HOW TO — Python 3.5.2 ドキュメント

nums = [1, 3, 2, 5]

# 非破壊的変更はsorted関数
ret = sorted(nums)
print(ret)
# => [1, 2, 3, 5] # 戻り値はソートされる
print(nums)
# => [1, 3, 2, 5] # もとのオブジェクトは変更されない

# 破壊的変更はlist型のsortメソッド
ret = nums.sort()
print(ret)
# => None # 戻り値はNone
print(nums)
# => [1, 2, 3, 5] # 破壊的変更が加わっている

一方、Rubyでは配列をソートしたければ、非破壊/破壊的メソッドのsort, sort!メソッドを使い分ければいいだけです。 また、「オブジェクトからメソッドを呼び出す」ことが徹底している分、 破壊的操作も「要するにインスタンスの内部の変数を書き換えているだけでしょ」と理解できました。

ref.xaio.jp

完全に余談ですが、PHPを書いていた(書かされていた)頃は、sort系の関数がいくつも用意されていたり、そのだいたいが破壊的操作だったり、全く理解できませんでした。

3. nilとfalse以外は全てtrue

勉強になったというか、プログラミング言語として分かりやすいのが「nilfalse以外は全てtrue扱いされる」というルールです。

www.rubylife.jp

例えば、正規表現にマッチするかどうかを調べるには、String型のmatchメソッドの戻り値をifに渡すだけです。 matchメソッドはtrue, falseを返すわけではなく、正規表現にマッチしたときはMatchDataオブジェクトを返し、マッチしなかったときはnilを返します

url = 'www.google.com'
if url.match(/google.com/)
  puts 'Google!'
end
# => Google!

Booleanを返すような特別なメソッドを用意することなく、nilを返すメソッドを使うことで、簡潔で分かりやすいコードを書くことができます。

Pythonでは、FalseNoneの他に、空文字や0なども偽として扱われます。

www.pythonweb.jp

Pythonのほうがルールが複雑でイケてないじゃん」と思われるかもしれませんが、 プログラマが持つべき心構え (The Zen of Python)の記事に書かれていた 最大公約数を求めるプログラムを見るとハッとするかもしれません。 (これも、y <= 0みたいに明示的に書いても良い気もしますが)

def gcd(x, y):
    while y:
        x, y = y, x % y
    return x

4. 便利なメソッドや書き方が多い

多様性は善」を一つのスローガン(?)に掲げているだけあり、 Rubyには様々な便利な機能やメソッドが用意されています。 特に、Pythonに無い機能で便利だと思ったのが以下の4つです。

  1. nilガード(||=)
  2. each_with_object
  3. 後置ifによる早期リターン
  4. メソッド名に!や?が使えること

nilガード(||=)

初心者プログラマーRubyに触るとまずググるのに困るアレ(||=)です。 変数が存在しない場合やハッシュのキーが無い場合(nilの場合)に変数を初期化することができます。

Sinatraで、あるGoogleのサービスのAPIを叩く社内用API(Google APIを叩く & DBにログを残す)を作る際、 「id(hogehoge_id)が引数で渡ってこない場合があるため、名前でidを検索する必要がある」ことがありました。

このような場合にnilガードが便利です。

# dataはAPIに渡す情報が入ったハッシュ
data['hogehoge_id'] ||= _search_hogehoge_id(data['hogehoge_name'])
_exec_api(data)

ちなみに、||=の「nilガード」という呼び名はメタプログラミングRubyの記述に倣ったものです。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

呼び名については、Rubyistの中でも意見が分かれているようです。レビューするとき不便ですね。

www.softantenna.com

each_with_object

私が最初にRubyを使っていて最初に感じたのが、「内包表記やジェネレーターの代わりになるもの無いの?」ということです。

もちろんmapselect等のメソッドもあるのですが、戻り値が配列に限られていますし、 簡単なフィルターもできないため、Pythonの〇〇内包表記に比べて使い心地が悪く感じます。

そんなPythonプログラマーの需要を満たすのがeach_with_objectメソッドです。

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

Pythonであれば、辞書内包表記を使って以下のように書くでしょう。

# get_titleはページタイトルを取得する関数
def create_url_table(urls)
  return {url: get_title(url) for url in urls if 'google' not in url}

Rubyでは同様のメソッドを次のように書けます。

# Rubyでは明示的にreturnを書かなくて良い
# ループした結果をハッシュ(デフォルト値では{})のキーに代入していく
def create_url_table(urls)
  urls.each_with_object({}) do |url, hash|
    hash[url] = get_title(url) unless url.match(/google/)
  end
end

宗教上の理由で破壊的操作を受け入れられない人以外は、便利に使えるメソッドだと思います。

qiita.com

ここからはeach_with_objectとは関係ない話です。

RubyPythonのような遅延評価が行いたいなら、Enumerableモジュールのlazyメソッドを呼び出せば、mapselectに遅延評価を適用することができます。

Rubyist Magazine - 無限リストを map 可能にする Enumerable#lazy

Pythonのジェネレーターみたいなことがしたいなら、Enumerator::Lazy.newからブロックを呼び出せばできなくはないようです。 …なんだかRubyっぽくないですが。

data = [[1], [2, '2'], ['a', 3, 'b'], [4], ['c']]
generator = Enumerator::Lazy.new(data) do |yielder, item|
  item.each do |x|
    yielder << x if x.is_a?(Integer)
  end
end

puts generator
# => #<Enumerator::Lazy:0x0055634bb03200>
puts generator.first
# => 1
puts generator.force
# => [1, 2, 3, 4]

一度取り出された値(1)がもう一度出てきているので、Pythonのジェネレーターとは少し挙動が違うみたいです。

後置ifによる早期リターン

後置ifも、早期リターンしやすいので便利です。

例えば、Twitterで自動リツイートするbotを作るため、ツイートの配列を渡すとリツイートするメソッドを作りたいとします。 そのとき、引数の配列にnilが含まれる可能性がある場合、その場合はreturnして逃してあげると、 eachメソッドを呼び出す際に変数がnilの場合のエラーを気にせずに済みます。

def post_retweets(tweets)
  return if tweets.nil?
  tweets.each do |t|
    # リツイートする処理
    _post_retweet(t[:tweet_id])
  end
end

mugenup-tech.hatenadiary.com

後置ifが素晴らしいのは、else句が無いことを明示的に示していることです(と個人的には思っています)。

例えばPythonで同様の処理を書くと以下のようになりますが、elseの場合の処理は必要ないの?それとも書き忘れているの?」と不安に感じると思います。 かといって明示的にelse句を書いてネストさせるのもちょっと…と感じてしまいます。

def post_retweets(tweets):
  if tweets is None:
    return
  for t in tweets:
    # リツイートする処理
    _post_retweet(t['tweet_id'])

メソッド名に!や?が使えること

?は真偽値を返すメソッドに、!は破壊的な(または注意すべき)メソッドに付けることが多いです。

Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 2.3.0)

ただし、!は破壊的メソッドだけに限らず、例えばハッシュのmergeメソッドupdateメソッドも破壊的であることに注意しましょう。

Rubyの微妙な点

Pythonに比べてプログラミングの自由度が大きい分、微妙だと感じるものも多いです。

ただし、これはプログラミング言語どうこうというより、言語ユーザーの文化的な違いの面が大きいのかもしれません。

1. Rubyオブジェクト指向にこだわりすぎている

Rubyでは、「そんなのいちいちクラス分けなくていいじゃん」と思うようなことでも、Rubyではクラスを分けたりします。例えば、標準ライブラリのnet/http等がそうです。Web APIを叩くときによく使います。

Rubyist Magazine - 標準添付ライブラリ紹介 【第 7 回】 net/http

Class: Net::HTTP (Ruby 2.3.1)

例えば、チャットワークのAPIを利用して、POSTでメッセージを投稿するようなものを想像しましょう。

curl -X POST -H "X-ChatWorkToken: xxxxx" -d "body=チャットワークに投稿" "https://api.chatwork.com/v1/rooms/*****/messages"

上の例を、Rubynet/httpを使ったコードに直すとこんな感じになります。(ただし、curl-to-rubyというサイトで作ったため、もっと簡単な書き方があるかもしれません)

require 'net/http'
require 'uri'

uri = URI.parse("https://api.chatwork.com/v1/rooms/*****/messages")
request = Net::HTTP::Post.new(uri)
request["X-Chatworktoken"] = "xxxxx"
request.set_form_data(
  "body" => "チャットワークに投稿",
)

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end

URI専用の型を作って、それをもとにNet::HTTP::Postインスタンスを作って、パラメータをつけ加えて実行するような形です。

同じ操作をPythonrequestsライブラリで行うと、必要なパラメータを辞書(ハッシュテーブル)で引数に与える形で実現しています。標準ではないものの、net/httpと似た用途でよく使われます。(標準のurllibopen-uriに対応するようなシンプルな用途で使われます。)

import requests

headers = {'X-ChatWorkToken': 'xxxxx'}
params = {'body': 'チャットワークに投稿'}
requets.post('https://api.chatwork.com/v1/rooms/*****/messages', headers=headers, params=params)

私はPythonの書き方のほうがシンプルで分かりやすいと思っています。

同じように、色々なgem(ライブラリ)を使う際に、引数や戻り値がいちいち専用の型のインスタンスで操作することが多く、「これって文字列型でいいじゃん…」と感じることが度々あります。

こちらの記事の後に、

qiita.com

同じリファクタリングPythonで行った記事を見ると、Ruby/Pythonでの文化の違いが分かると思います。

qiita.com

2. 変な記号がいっぱいある

Rubyに慣れていない間、既存のコードを読み解く際、変な記号がいっぱいあって困りました。例えばclass << selfという記述があり、ようやくググると特異クラスという単語が現れ(特異クラスってなんだ?)…みたいな感じに、ちょっとした記述が分からずにハマることが案外多かったです。

Rubyist Magazine - Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)

これを友人のRubyプログラマーに愚痴ったら「Perlにはもっと意味不明な記号があるよ」と言われました。ひとまず下のページをブックマークしておくといいでしょう。

Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 2.3.0)

また、文字列型('string')とシンボル型(:symbol)が別々に存在することも、おそらく、ハッシュのキーや言語自体の機能を操作するために、イミュータブルな(破壊的変更ができない)文字列型のようなものが必要だったんじゃないかと思います。 ただし、別に文字列型だけでも対応できたんじゃないかなと思います。

togetter.com

ちなみにPythonでは通常の文字列型が破壊的変更不可能なので、Rubyのシンボル型のような役割も兼ねています。代わりに、ハッシュテーブルのキーにミュータブルなオブジェクトが使えないようになっており、イミュータブルな配列型(タプル型)が用意されている点がちょっと不自然かもしれません。

www.yoheim.net

3. ブロック(ラムダ式)が強力すぎる

Rubyのブロック(メソッドからラムダ式を呼び出す機能)が強力すぎる分、気をつけないとすぐにごちゃごちゃしたコードになる印象があります。

Pythonではラムダ式の表現力を意図的に落としていて、「これ以上複雑にするなら関数分けてね」と制限しているのですが、 Rubyでは「プログラマーよ、望むままを行え」ということを戒律にしているようです。ベルセルクの使徒みたいです。

Rubyist Magazine - Rubyist のための他言語探訪 【第 1 回】 Python

こちらの記事に書かれていますが、

しかし、この文法では、ブロックを含む構文は値を持つ式として用いることができませんから、Rubyで頻繁に使われるブロック付きメソッド呼び出しのようなことはできません。 Python には名前のない関数を作る lambda という文法がありますが、関数本体部分には式ひとつしかかけないという大きな制限があるため、条件分岐ひとつ使うことができません。 そのような場合、Python では名前を付けた関数を定義し、その関数を引数として使うのが一般的のようです。 Ruby では

ary.map {|x| x**2}

となるものが、Python では

map(lambda x: x**2, ary)

となり、lambda の本体が1つの式では表現しきれなくなると

def mapper(x): .....

map(mapper, ary)

と書き換える必要があります。ブロックによるうれしさとのトレードオフの関係と言っても良いかもしれません。

Pythonでは「リストの要素一つを操作する小さい関数を作る」 → 「その関数をmapやリスト内包表記でリスト全体に適用する」という順序で考えていたので、 Rubymapのブロックの中にごちゃごちゃ書くのに違和感があります。

先輩から「if文の無いeach文はmapに書き換えられるよ!」と説明を受けたことがあるのですが、 私はmapは配列を数学の写像として操作する関数やメソッドで、 eachとは違うニュアンスで捉えていた(実行する順序が関係ないとか)のでちょっとビックリしました。

ただし、強力なメソッドを適切に使うことで、意図の伝わりやすいコードになるとも思うので、注意すれば問題ないかなと思っています。

まとめ & おすすめの書籍

意外と長くなってしまいました。

プログラミング初心者の後輩が入ってきたとして、↑のような説明してもあんまり伝わらない(少なくともPython知ってないと分からない)し、 私自身まだ糞みたいなプログラマーなので、どこかに認識違い等があると思います。

Ruby勉強したい人は次の2冊をおすすめします。めっちゃ無難な取り合わせです。

初めてのRuby

初めてのRuby

Effective Ruby

Effective Ruby

逆に、RubyプログラマーPythonを勉強したいなら、以下の3点を押さえておけば、ちゃんとしたプログラムが書けると思います。

  1. リスト内包表記
  2. ジェネレーター(遅延評価)
  3. ふつうに定義した関数がオブジェクトであること

その他の困る点は、先人が素晴らしい記事を書いてくださっているのでそちらを参考にしましょう。

qiita.com

「2と3のどちらのバージョンを身につければいいの?」という人もいると思いますが、 3系で困る(例えば使いたいライブラリが2系のみ)ことは全くといいほど無くなりました。 3系を勉強しましょう。

入門 Python 3

入門 Python 3

【本】『ウェブマーケティングという茶番』『やりなおし!地理の教科書』など読みました

技術書っぽくないやつですが、昨日2冊読みました。

ウェブマーケティングという茶番

タイトルに惹かれて買いました。リスティング広告SEOなどの広告代理店の話です。

胡散臭いと思われているこの業界を変えるため、同業者を敵に回す覚悟で、業界の実態を暴き、本当の魅力を伝えようと本書の執筆を決意!

弊社代表、後藤晴伸が執筆した本経営者新書 「ウェブマーケティングという茶番」幻冬舎より出版されました。 │後藤ブランド株式会社 真のWEBマーケティングをプロデュース | 後藤ブランド

  • GDN(Google Display Network)等を利用する際は、きちんとターゲティングしなきゃ無駄に広告費かかるから代理店にも圧をかけなきゃダメ
    • 例えばスマホアプリに出す広告は間違ってクリックするユーザーも多いので除外すべき
    • 同業他社が狙っておらず、自分たちの顧客セグメントに合ったブルーオーシャンのセグメントを狙うべき
  • 制作やSEO、広告まで総合的に運用できる担当者はかなり貴重
    • ダメな会社や担当者の見極め方
  • 「ここは大きな会社と取引があるから大丈夫」 → 中小の優先度が低く後回しにされる

みたいな具体的で、どこかで聞いたような話も多く、 多少なりともウェブマーケティングに関わった人なら、学ぶところがあると思います。 代理店側の都合や事情を知るのに役に立つかもしれません。

(最後にちゃっかり自分の会社の宣伝も行っています!)

ウェブマーケティングという茶番 (経営者新書)

ウェブマーケティングという茶番 (経営者新書)

やりなおし!地理の教科書

「え〜!○○県出身なの!?〇〇県は××が特産なんだよね!」みたいな話や、不動産関連のデータを各地域ごとに分析した、みたいな話に全くついていけないので読みました。

古代から現代まで、いろいろな時代に関わる話が載っていました。「古代にナントカっていう令が出されて漢字二文字の地名が多いんだよ!その際に沖→隠岐など二文字地名が増えたらしい」などなど。

個人的に印象に残った話題をメモしておきます。

  • 本来は同名の市町村名はNGだが、法律の施工のタイミングや、合併を推し進めるための緩和策(既存の市町村がOK出せばOK)のために重複している地名がある
    • エンジニア的には勘弁してほしいです。制約のないデータベースみたい…
  • 今はドーナツ化現象は緩和し、通勤が楽な都心に回帰して来ている
  • 全市町村の46%以上が限界集落、そのうち1/3程度が65歳以上しか住人のいない超限界集落
  • 中核市特例市の要件は備えているのに、住人の経済的な負担増を嫌って申請しない地域もある

なんというか、今の市区町村といった区分って、「きちんとデータにもとづいて整理されたもの」というより、「それぞれの地域やそこの住人が、自分の利やメンツに則って行動した結果が今の地域」と考えたほうがいいなあと思いました。その分歴史を勉強するのは楽しそうですが。

会社に残っているようなデータもこんな感じですよね。

やりなおし! 地理の教科書

やりなおし! 地理の教科書