読者です 読者をやめる 読者になる 読者になる

歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】辞書のキーにデフォルト値をセットする

最近Python自然言語処理をしていて、複数のライブラリを利用していると、どうしてもリストや辞書のややこしい変換が増えてきます。

categories = ['サーバル', 'かばん', 'サーバル']
lines = [
    ['ここ', 'は', 'さばんな', 'ちほー'],
    ['食べ', 'ない', 'で', 'ください'],
    ['すっごーい!']
]

これを、キャラクターごとに利用している単語を集計したいと思います。具体的には、以下のような辞書に変換したいとします。

{
    'サーバル': ['ここ', 'は', 'さばんな', 'ちほー', 'すっごーい!'],
    'かばん': ['食べ', 'ない', 'で', 'ください']
}

Rubyであれば、nilガードを使って、サクッとハッシュの値を初期化すると思います。

# Ruby
categories.zip(lines).each_with_object({}) do |(c, l), hash|
  hash[c] ||= []
  hash[c].concat(l)
end

ところが、Pythonで同じことをやろうとすると、「キーが存在しないときに初期化する」という処理で、どうしても冗長になってしまいます。

result = {}
for c. l in zip(catagories, lines):
    if not c in result:
        result[c] = []
    result[c] += l

こんなときは、collections.defaultdictを使うと良さそうです。指定の型のデフォルト値(この場合は空のリスト)で初期化した値を辞書のキーに持たせることができます。

from collections import defaultdict

result = defaultdict(list)
for c, l in zip(catagories, lines):
    result[c] += l

厳密にはdict型ではないものの、defaultdictはdict型のサブクラスで、メソッドもほぼ共通しています。

参考:

8.3. collections — コンテナデータ型 — Python 3.6.1 ドキュメント

このような「オブジェクトの操作に向いているデータ型を使おう」といった感覚は、ケント・ベックSmalltalk本に詳しく書かれていました。

言語はSmalltalkですが、Pythonでもよく使われるSet型についての解説(値の重複チェックはオブジェクトにまかせてしまおう!)もあり、オブジェクトの操作の感覚をつかむのに勉強になりました。

ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集

ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集

  • 作者: ケントベック,Kent Beck,梅沢真史,皆川誠,小黒直樹,森島みどり
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2003/03
  • メディア: 単行本
  • 購入: 7人 クリック: 94回
  • この商品を含むブログ (55件) を見る

【Haskell】optparse-genericを使ってコマンドラインツールを作るまで

Haskellは優れた型システムによって見通しの良いプログラミングができるものの、アプリケーションを作る際にはどうしても敷居が高く感じてしまっていました。

というのも、PythonRubyなどのスクリプト言語では豊富なパッケージの組み合わせで小さなプログラムならサクッと作れてしまうことを考えると、自分にとって新しい言語をわざわざ使うメリットを考えてしまい、単なる教養やお勉強の領域だけにとどまっていました。

著名で使い勝手の良いライブラリを調べる

Haskellの"package archive"であるHackageには多数のパッケージが登録されているものの、この中のどれを使えばいいのか、なかなか候補が絞り込めずにいました。

翻ってPythonについて考えると、私はまずawesome-pythonリポジトリを参考にし、ここに自分の欲しい機能を見て(「コマンドライン引数をパースするライブラリが欲しいからcommand-line-toolsの項目を見よう!」)、紹介されている中から自分にとって使いやすそうなものを選びます(「clickがシンプルで良さそう」)。それで良い物がなければPyPIを直接検索します。

同様にawesom-haskellというリポジトリがあれば、そこから調べていけば助かります。実際にGithub上に2つ存在しました。

こちらのリポジトリでは1000件以上のスターが付いており、Pythonのものと同様、各項目に整理されています。

github.com

もう一つのほうは整理されていないものの、上記のリポジトリで紹介されていないパッケージもあります。例えばコマンドラインパーサーもいくつか紹介されています。

github.com

この中から、使い勝手の良さそうなoptparse-genericというパッケージを使ってみます。

github.com

stackを使って開発してみる

Haskellのビルドツールであるstackを利用して開発してみます。

stackが何かという話は、Rubyプログラマーは「Haskell Stack でライブラリを安定させる | Netsphere Laboratories」を読めば分かると思います。

基本的なコンセプトは Ruby のための Bundler と同じ。プロジェクトが直接必要とするライブラリと, さらに依存するライブラリについて、システムグローバルにインストールされたパッケージで不足する場合, プロジェクト内にインストールしてしまう。

新規プロジェクト作成

開発用のディレクトリで新規プロジェクトを作成します。

stack new haskell-cli

ディレクトリ構成はこんなのが出てきます。

$ cd haskell-cli; tree
.
├── LICENSE
├── README.md
├── Setup.hs
├── app
│   └── Main.hs
├── haskell-cli.cabal
├── src
│   └── Lib.hs
├── stack.yaml
└── test
    └── Spec.hs

3 directories, 8 files

依存ライブラリの追加

(プロジェクト名).cabalのファイルを編集します。本来は他の項目(ホームページのアドレス等)も変更する必要があるのですが、とりあえず動かすだけなのでこれだけで。

executable haskell-cli-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , haskell-cli
                     , optparse-generic -- この行を追加
  default-language:    Haskell2010

サンプルコードをapp/Main.hsに書き写します。

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}

import Options.Generic

data Example = Example { foo :: Int, bar :: Double } deriving (Generic, Show)

instance ParseRecord Example

main = do
    x <- getRecord "Test program"
    print (x :: Example)

ビルドして使ってみる

プロジェクトのディレクトリで実行すればビルドされます。

stack build

また、stack execコマンドでビルドしたものを使うことができます。

$ stack exec haskell-cli-exe -- --foo 1 --bar 2.5
Example {foo = 1, bar = 2.5}

ビルド先のディレクトリに移動すれば、直接実行することもできます。

$./haskell-cli-exe --foo 1 --bar 2.5
Example {foo = 1, bar = 2.5}

$ # 引数が足りないとエラーを出してくれる
$ ./haskell-cli-exe --foo 1 
Missing: --bar DOUBLE

Usage: haskell-cli-exe --foo INT --bar DOUBLE

さらに詳しく知りたい方は、以下の記事を読むと良いと思います。

qiita.com

最後に

おおまかな開発フローは確認できたので、サンプルコードではなく実際のコマンドラインツールを今後作りたいと思います。

こんなの作ろうと考えています。

【Haskell】vim上でHaskellの構文チェックを行う

今まで完全に教養としてHaskellを勉強していた(Pythonの型注釈に必要な感覚を養いたい、内部状態の少ないコードを書きたい)のですが、「この問題はHaskellなら上手く解けるんじゃないか」というものを思いついたので、初めて自分の頭で考えたコードをHaskellで書いています。

Haskellには強力な型システムがあるので間違ったコードを書きづらいのですが、少しコードを変えるたびにコンパイルを通すのはさすがに面倒です。そのため、普段利用しているエディター(vim)から静的解析ツールを呼び出せるように設定することにします。

こちらの記事で紹介されているhaskell-vim-nowでいろいろな開発ツールが導入できるようですが、私はエディターにいろいろ設定しても結局使いこなせないので、今後必要に応じて導入しようと思います。Pythonのプログラミングでもflake8と簡単な補完以外は使ってないので…。

qiita.com

というわけで今回は次の2項目を設定しようと思います。

特にhlintは命名規則やポイントフリースタイルの有無など、「Haskellらしくないコード」を検知してくれるようなので、勉強目的でも助かりそうです。

qiita.com

stackのインストー

Haskellの現在の事実的な標準のビルドツールであるstackをインストールします。stackについてざっくり知りたい方は以下の記事を読みましょう。

qiita.com

公式サイトにもある通り、UNIX系のOSであれば

curl -sSL https://get.haskellstack.org/ | sh

または

wget -qO- https://get.haskellstack.org/ | sh

でインストールできます。

Mac OS Xではbrewでもインストールできますが、公式サイト曰く「unofficial and lag slightly behind new Stack releases」とのことなので、私はcurlを使う方法でやりました。

brew install haskell-stack

念のため、現在インストールされるバージョンも記載しておきます。

$ stack --version
Version 1.4.0, Git revision e714f1dd3fade19496d91bd6a017e435a96a6bcd (4640 commits) x86_64 hpack-0.17.0

stackでghc-modをインストールする

こちらも公式サイトの手順に従います。会社のすごい先輩は事あるごとに「ネットの記事を参考にするのも良いが、基本的には公式サイトの手順に従え。場当たり的な対処はするな」と言っているので、皆さんも私の言うことは信用せず、以下のURLに目を通してください。

github.com

今回はプロジェクトごとではなく、グローバルな環境に入れてしまいたいので、Global installationの手順に従い、以下のコマンドをホームディレクトリで実行します。同時に、hlintもインストールしてしまいましょう。

stack install ghc-mod hlint

ghc-modの公式ページではcabalを使った手順が推奨されていますが、以下の記事で「stack ghcのバージョンとghc-modをビルドしたghcが食い違ってた」とき、うまく動作しないことがあるようです。

qiita.com

素直にパスを通します。stackを使ってインストールした場合は~/.local/binghc-modのバイナリが保存されているので、~/.bash_profileに以下を追記します。

export PATH=$PATH:~/.local/bin

vimの設定

ghc-modを利用したvimプラグインは、まずコード補完を目的とするneco-ghcが開発され、その他の機能(型の自動チェックとかLintとか)はghcmod-vimで提供されているようです。

eagletmt.hateblo.jp

私はPythonのflake8(文法チェック)のような機能を期待しているので、まずは基本的にhlintを利用し、より詳しい情報が欲しい場合にghcmod-vimも一応呼び出すようにしたいと思います。コード補完も欲しくなったらneco-ghcも検討します。

私はvimプラグイン管理ツールとして設定がシンプルだというvim-plugを利用しているので、こんな感じで記述していきます。

(ghcmod-vimvimprocに依存しているようです)

let g:syntastic_haskell_checkers = ["hlint"]

call plug#begin('~/.vim/plugged')
Plug 'scrooloose/syntastic'
Plug 'Shougo/vimproc', {'do' : 'make'}
Plug 'eagletmt/ghcmod-vim'
call plug#end()

詳しい利用方法は、vim-plugの公式サイトを読んでください。

使ってみる

Haskell で hlint を使用したコードチェック - C++でゲームプログラミング」のHaskellコードをvimで保存すると、hlintがコードの悪い部分と行を教えてくれます。

f:id:takeshi0406:20170514232251p:plain

また、:GhcModCheckコマンドを実行すると、ghcmod-vimコンパイラのエラーや警告を表示します。

f:id:takeshi0406:20170514232724p:plain

他にもいろいろできそうですが、とりあえずこれくらいで。

Haskellについて余談

ちょっとした余談ですが、もしインターネット上の資料でHaskellを学びたいという人はHaskell Day 2016チュートリアルから追うことをおすすめします。 他の情報源から追うと、古い情報があったり、Haskell経験者じゃないと難しい表現などが多く、情報の取捨選択が難しいと思います。

現在の事実的標準のビルドツールであるstackの説明なども詳しく、「ちょっと勉強したいだけなのに開発環境を整えるところで詰まってる」ような状況にはハマりにくくなると思います。

qiita.com

Haskellのコードを書く感覚をつかむには『すごいHaskellたのしく学ぼう!』がおすすめです。(その分、開発環境などの情報は古いです)

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

あと、こちらの本も「すごく分厚いけど、関数型言語を学ぶのに必要な感覚を丁寧に説明していて良かった」「すごいHaskellは、必要な概念の定義→利用例という説明が多くて『ちょっと待って!』と思うことが多かったけど、この本では無かった」ととある友だちから評判が良かったです(私はまだ読んでませんが…)。

Haskell 教養としての関数型プログラミング

Haskell 教養としての関数型プログラミング

表紙の「Haskellの美しさを知っている人は、人生に絶望することはない。Haskellで世界を変えたい。」という文言の威圧感がヤバいです。

また、日本HaskellユーザーグループのSlackも開放されています。私もROM専で登録してみましたが、Haskellの議論が日本語で読める貴重な場だと感じています。

haskell.jp

【アルゴリズム】検索エンジンで重要なトップnソートについてまとめておく

検索エンジンやレコメンドエンジンを昔実装していた先輩から、飲み会で

という話を聞きました。

ところが、私は低レイヤーの言語(CやC++)から逃げてPythonを始めたような人間なので、残念ながら「アルゴリズムとデータ構造」と呼ばれるような分野は全く詳しくありません。

しかし、社内には既に数学や機械学習・数理計画法の知識では敵わない人がいるため、私は生き延びるためには実装やアーキテクチャ(要するに数理モデルを「検索エンジン」として使えるようにする技術)を勉強しなければいけないと思っています。

という話はさておき、今後これらの分野を勉強するときのために、「トップnソート」関連の記事についてポインタを残しておこうと思います。

まずはこちらの記事。"top n sort"ではなく"top k-selection"と呼んでいますが、同じアルゴリズムだと思います。

qiita.com

プログラムを書くお仕事をしていると、いろんな場面で top-k selection をしなきゃいけない状況にちょくちょく出くわすことがあるかと思います。もちろん RDBMS を使っていれば、ORDER BY sort_column LIMIT k とすることでさくっと top-k selection が実現できるわけですが、RDBMS の外で top-k selection をしなきゃいけない状況だって (年に 2〜3 回もあるかは個人差がありますが)、人生の中で 1〜2 回は遭遇するんじゃないかと思います。

非常にわかります。私の場合、今のところ運良く「nが小さく、全部ソートしてしまっても遅くなかったケース」「一番大きなものだけ選ぶケース」にしか当たっていないので実装せずに済んでいるのですが。

こちらの記事のコメントで、言われている「mikioさんのページ」は、

本当は、mikioさんがかなり色々と実験をされていたので、そこにリンクが張れるとよかったんですが、ページが消えてるみたいで、見つかりませんでした……。

別の記事でも言及があったため、そのURLをインターネットアーカイブを探ると見つかりました。

開発メモ: トップNソートの検討

データベースに対して、ある順序でソートした時の最初の何件かが欲しいというクエリを投げることはよくあるだろう。SNSで言えば、誰かのコンテンツの最新10件を表示するとかいう場合だ。SQLだと "ORDER BY timestamp DESC LIMIT 10" とかいう感じ。同じような操作は全文検索システムのスコアリングでも定番である。俺もよく自分で実装するわけだが、その度に適当な試行錯誤をして時間がもったいないので、今回は入念に調べて決定版を出そうじゃないか。

こちらの記事では、実際にC++ヒープソートクイックソートを改造して実装しています。

なお、「レコメンドエンジンって、要するに検索エンジンの特殊なもの」ということは、以下の本の「Solrをレコメンドエンジンとして使う」という章でも書かれていました。検索エンジンの性能評価についても書かれていたのできちんとやればかなり勉強になりそうです。

[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)

[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)

本当は、業務でElastic Searchを使うような案件ができれば良いのですが、今のところそんな機会は無さそうです。仕方ないのでプライベートでAWS Lambdaを使ってクローラーを作り、まずは検索サービスを作るためのデータを集めようと画策しています。

【Python3.6】AWS Lambdaでツイッターを検索して自動リツイートする

先程の記事に引き続き、AWS LambdaでPython3.6が使えるようになったので、過去にPython2.7で書いたバッチ処理のレガシーコードをLambdaに移行しています。

まずは、下の記事で書いた「ツイッターを検索した結果を自動リツイートする」コードを移行します。今見るとけっこうコード酷いですね。

kiito.hatenablog.com

Pythonのコードを書き直す

AWS Lambdaの流儀に従ってコードを書いていきます。

ここではlambda_function.pylambda_handlerという関数を用意して、実行するように設定することとします。

後述しますが、アクセストークン等は環境変数で渡しています。

import os
import time
from datetime import datetime
import twitter


TARGET_TERMS = ['ファンタジスタドール', '鵜野うずめ']
API_ENV_KEY = ['CONSUMER_KEY', 'CONSUMER_SECRET', 'ACCESS_TOKEN_KEY', 'ACCESS_TOKEN_SECRET']
COUNT = 100

def lambda_handler(event, context):
    client = TwitterClient()
    client.run(TARGET_TERMS)
    return True


class TwitterClient(object):
    def __init__(self):
        self.client = twitter.Api(*[os.environ[k] for k in API_ENV_KEY])
        self.nowtime = int(time.mktime(datetime.now().timetuple()))
        self.begin_time = self.nowtime - 60 * 60
    
    def run(self, terms):
        for term in terms:
            for tweet in self._search_tweet(term):
                if tweet.created_at_in_seconds in range(self.begin_time, self.nowtime):
                    self._post_retweet(tweet.id)

    def _search_tweet(self, term):
        return self.client.GetSearch(term=term, count=COUNT, result_type='recent')

    def _post_retweet(self, tweet_id):
        try:
            self.client.PostRetweet(tweet_id)
        except:
            print(f'{tweet_id}は既にリツイートされています')

ちなみにpython-twitterというライブラリを使っています。

github.com

zipで固めてアップロードする

先程の記事で用意したDockerイメージを使って、依存ライブラリも含めたzipファイルを用意しました。

kiito.hatenablog.com

requirements.txtには以下を記述しています。

python-twitter

環境変数でアクセストークン他を設定する

AWS Lambdaでは環境変数が設定できるので、アクセストークンなどはこちらから渡すことにします。

dev.classmethod.jp

こんな感じです。

f:id:takeshi0406:20170506170833p:plain

本来は「暗号化ヘルパーを有効にする」を選択すべきだと思うのですが、「どうせ自分以外見ないだろう」という甘い考えでやっていません。

本当はDockerで環境変数や依存ライブラリまで再現した開発環境を用意すればかっこよかったのですが、今回は小規模なコードなので特に用意しませんでした。

その他の設定

あとは以下の設定を行いました。特に難しいことはないと思います。

  • 「トリガー」で定期的に実行する
  • タイムアウト時間を3秒から3分まで長く設定しなおす(3秒ではさすがに打ち切られてしまう)

【Python3.6】AWS Lambdaを再現するDocker Imageのdocker-lambdaを使ってみた

この間AWS LambdaでPython 3.6がサポートされたので、レンタルサーバーで動かしているTwitterの自動投稿やクローラーを移行しようと画策しています。

AWS LambdaがPython3.6に対応したので使ってみた | Developers.IO

外部ライブラリ(twitterライブラリやBeautifulSoupなど)を利用使いたいので、zipで固めてデプロイする方法でいきたいのですが、いろいろ面倒そうなのでまずはその辺の煩雑な作業を自動化する手段でやってみました。

いろいろ調べていくうちに、以下の記事に紹介されている、「手元の環境で擬似的にLambdaを実行する」というDockerイメージが良さそうだったので使ってみます。

AWS Lambdaの開発環境を構築~docker-lambdaの紹介~ – クリエイティブ - ブログ - 株式会社テレビ朝日メディアプレックス

今回は Lambdaのデバッグ効率を上げるdocker-lambdaの設定手順を紹介します。

名前からイメージ出来る通り、Dockerで疑似的にLambdaを実行するという物になります。

github.com

ただし、Docker Imageが2GB弱ほどあるので、多少ハードディスクの容量は食ってしまうので、もしローカル環境で使うならば注意が必要かもしれません。

私はMac OS Xを利用しているので、公式サイトの"Docker for Mac"からダウンロードしました。

実は未だにちゃんとDockerを使ったことが無かったのですが、次のサイトを参考に簡単に勉強すれば、問題なく使うことができました。

www.atmarkit.co.jp

docker-lambdaを試しに実行する

lambda_function.pyという名前で現在のこのようなファイルを用意します。

def lambda_handler(event, context):   
    print("value1 = " + event['key1'])
    return event['key1']

公式のExampleにもある通り、引数にeventの値となるjsonを渡して実行します。

$ ls
lambda_function.py
$ docker run -v "$PWD":/var/task lambci/lambda:python3.6 lambda_function.lambda_handler '{"key1": "key2"}'
START RequestId: bcc19516-283b-485d-b728-69497756be19 Version: $LATEST
value1 = key2
END RequestId: bcc19516-283b-485d-b728-69497756be19
REPORT RequestId: bcc19516-283b-485d-b728-69497756be19 Duration: 32 ms Billed Duration: 132.0 ms Memory Size: 1536 MB Max Memory Used: 18 MB
"key2"

docker-lambdaでデプロイパッケージを作成する

次は、デプロイパッケージをdockerを使って作成します。

ローカルの環境(Mac OS X)でも同じことはできるのですが、特にPure Pythonでないライブラリを利用する際にエラーが出てしまうそうなので、できるだけ実際のAWSに近い環境でライブラリが用意できるので変なエラーに詰まることが少なくなることが期待されます。

docs.aws.amazon.com

Lambda 関数を作成するには、最初に Lambda 関数デプロイパッケージ (コードと依存関係で構成される .zip ファイル) を作成します。

以下の記事と、docker-lambdaリポジトリの「Create your own Docker image for finer control:」の項目を参考にして簡単にPython3.6をビルドできるdocker imageを作ります。

qiita.com

デプロイ用のDocker Imageを用意する

以下のようなDockerfileを用意します。

FROM lambci/lambda:build-python3.6

ENV AWS_DEFAULT_REGION us-east-1

ADD . .

CMD pip3 install -r requirements.txt -t /var/task && \
  zip -9 deploy_package.zip lambda_function.py && \
  zip -r9 deploy_package.zip *

mylambdaという名前でイメージを作ります。

docker build -t mylambda .

コードを用意する

作業ディレクトリで、lambda_function.pyとrequirements.txtを用意します(というか、この名前でファイルが存在していることを前提にDockerfileを書いています)。

$ ls
lambda_function.py requirements.txt

ひとまず、著名な外部ライブラリであるrequestsを使ったコードを書いてみます。

ひとまず、lambda_function.pyという名前で、こちらの記事で紹介されているグローバルIPを確認するAPIを呼び出しています。

import requests


def lambda_handler(event, context):
    res = requests.get('http://httpbin.org/ip')
    return res.json()

requirements.txtはこのような内容です。具体的なバージョンは特に指定していません。

requests

Dockerの環境内でzipで固める

次のコードを実行することで、Dockerfileの"RUN"の項目で指定されているシェルスクリプトが動き、deploy_package.zipが吐き出されます。

docker run -v "$PWD":/var/task mylambda

このdeploy_package.zipをアップロードすると、awscli等でアップロードすればLambda上で動かすことができます。

f:id:takeshi0406:20170506144610p:plain

まとめ

docker-lambdaを使ってAWS Lambdaの開発が便利になることを簡単に確認しました。

また、zipファイルとdevを同じディレクトリに保存しているので、何度も試行錯誤を行う際は微妙かもしれないので、適宜書き換えつつ利用しようと思います。

手軽に環境を捨てられるのもDockerの利点だと思うので。