技術情報棚卸し(平日限定)

todoa2cの技術情報棚卸しです。平日限定ってことはアレだ。言わせんな恥ずかしい。

Codeshipを使ったApp Engineへの継続的デリバリー

IntelliJ IDEAからApp Engineへのデプロイが簡単にできることは以前書いたのですが、 自分のIntelliJ IDEAのローカルフォルダの情報がそのままデプロイされてしまうので、 コミットしていないコードがそのままデプロイされてしまう危険性があります。 また、デプロイの作業が自分しかできない、暗黙知化されるのも良くないわけです。

こういった問題の解決策として継続的デリバリーを行うべく、 実現できるサービスを探したところ、ありました。 Codeshipというサービスです。

Codeshipは、扱えるコード、コードのホスト先だけでなく、 コードを動かすクラウド環境(AWSやDigital Oceanなど)も選べるし、その際に必要となる デプロイやプロビジョニングツールも提供されており、 テストから環境構築してデプロイまでを一手に引き受けてくれるサービスです。

今回は、Bitbucketに置いてある自分のプライベートリポジトリを監視し、 コードのPushがあったタイミングでテストを行い、 かつmasterブランチに対するコミットがあった時点でApp Engineにデプロイする、 というところまでをやってみました。

基本的な実現方法は、下記をご参照ください。こっちの方が分かりやすいです。

Continuous Deployment for Django apps from Bitbucket to Google App Engine – Codeship

工夫した点は、設定面の工夫は特にありませんが、デプロイの仕方だけは少し工夫しています。 GitのブランチをGit-flow風にして、developを開発用ブランチ、masterをリリース用ブランチにして、 リリース時には、developの変更点をmasterにマージしてもらうようなPull Requestを Bitbucket上で作成します。

デプロイ用Pull Requestの変更点

こうすることで、どのコミットがリリースに含まれるかが、リリース用Pull Requestを見れば 一目瞭然になるわけです。

※アイデアは GitHub 時代のデプロイ戦略 – naoyaのはてなダイアリーです。

あとはデプロイはCodeshipにお願いすると。

Codeshipによるデプロイ

これでBitbucket上からのリリースもできるようになり、非常に快適になりました。 あとは、今はまだステージング環境のようなものを用意していないのですが、 developブランチにコミットがあった場合にステージング環境にデプロイもする、という設定を 追加すると良いかもしれないですね。

Codeshipは1ヶ月に50回のビルドまでであれば無料で使うことが出来ますので、 ご興味のある方は、是非是非お試しください。

App Engine/Pythonでハマった初心者向けトラップ

早速いくつかトラップにハマりました。

requestsパッケージがApp Engineで動かない

App Engine/PythonはURL Fetch Python API Overview にあるように、標準ライブラリのurllib, urllib2, httplibであればそのままURL Fetchが使えました。

一方、GitHubのAPIを叩く際に使っていたgithub3.pyは 内部的にrequestsライブラリを使用しているわけですが、 このrequestsライブラリですと、App Engineでは動かないのです。 更にこのrequestsライブラリの中で使われているurllib3はApp Engine内で動くらしいのですが…動かず。無念。

結局他のGitHub APIライブラリを使用しました。

GoogleAppEngineLauncher.appアップデート後の罠

GoogleAppEngineLauncher.appアップデート後は、一度は起動しないと最新モジュールが 適用されないみたいです。 私はIntelliJ IDEAを使っており、そのIntelliJ IDEAが参照するgoogle_appengineフォルダは /usr/local/google_appengine ではなく、GoogleAppEngineLauncher.app内の結構深いところにある google_appengineフォルダを参照します。

で、GoogleAppEngineLauncher.app がアップデートされたあとは、この google_appengineフォルダが 見当たらないのです。その代わり、google_appengine.zip はある。 一度起動することにより、google_appengine.zipが解凍されて google_appengineフォルダが 作られる模様ですね。。。気づかんわこれ。

こちらからは以上です。

App Engine用言語をPythonにした件…

はじめに

GoでGoogle App Engineを触るテストを 書いておきながら、諸般の事情により、Pythonで書きなおすことにしました。 書き直すと言っても、大した進んでいないので、また1から書き直しという感じですね。

準備

今回はMacで開発しているので、ライブラリはここからダウンロードし、インストール&実行すればOK。

Download the Google App Engine SDK – Google App Engine – Google Developers

Pythonはvirtualenv経由だとうまく動かないもようなので、Pyenvで特定の2.7系バージョンを作る。

開発環境はIntelliJ IDEAのPythonプラグイン + App Engineサポートを利用。 間違ってもFlaskを使いたいからと言って、Flask Templateを選んではいけない。あとで自分でライブラリを ダウンロードすること。そうしなければ、App EngineのライブラリがIntelliJ IDEA側で認識してくれなかった (別途不具合報告予定)。

書いてみる

ライブラリは requirements.txt あたりを用意し、下記コマンドを実行することで、 ライブラリがlibフォルダに置かれる。

1
pip install -r requirements.txt -t lib

また、appengine_config.py を用意し、上記のlibフォルダをライブラリフォルダとして認識するように、 下記を記述する。

1
2
3
4
5
import os
import sys
ROOTPATH = os.path.dirname(__file__)
LIBPATH = os.path.join(ROOTPATH, 'lib')
sys.path.append(LIBPATH)

その他コードは割愛するけれど、Flaskのフレームワークとしての薄さと 3rd-partyライブラリによる拡張性の高さは結構好み。 とは言えDjangoをまじめに学習したことがないので、別途 大阪Pythonユーザの集まり 2014/05の Djangoセッションで学習してみようかと思う。

デプロイ

デプロイは、ローカル&本番環境ともに、とりあえず簡単に、IntelliJ IDEAから試す。 ローカル環境の場合は、App Engine Serverから起動する事ができる。

本番環境は、メニューのToolsからGoogle App Engine → Upload App Engine app… から アップロードできる。簡単。

簡単だけど、継続的デリバリーができなくなるので、別途デプロイ方法を検討する。

大阪Pythonユーザの集まり 2014/05 を募集開始しました ‘#osakapy

第2回の大阪Pythonユーザの集まりを、5月29日に行います。

大阪Pythonユーザの集まり 2014/05 – connpass

  • 日時 2014/05/29 19:00
  • 場所 堂島アバンザ21F

Pythonユーザはもちろんのこと、 Pythonに興味を持っていただいている方のご参加もお待ちしております。

今時点では、下記3名が発表予定です。私も参加します。

  • @whosaysni Django関連
  • @todogzm とりあえず何か。多分Python 2/3移行関連?
  • @Ido GAE/PyについてLT

誰か少しでも発表できることがありましたら、ご参加をお待ちしております。 個人的にはPython 3.4の新機能を話せる人がいたら、お話いただけると嬉しい次第です。

また、発表者が少なかった場合には、グループディスカッションを考えています。 何についてディスカッションしていただくかはまだ考えていませんが、 結果として参加者も勉強会に貢献したぜ感を感じられるような勉強会にできればなぁと 漠然と考えております。

懇親会も行う予定ですので、こちらも是非ご参加ください。 前回の反省(?)を踏まえ、早めにアナウンスします。

ちなみに、アンケートを募集したものの、サンプル数が少なすぎました…。

大阪Pythonユーザの集まりの意見募集中 ‘#osakapy

大阪Pythonユーザの集まりについて、GW明けにまた行おうと考えています。 第1回は平日夜の開催で、発表形式の会でしたが、他にも色々やってみたいという声がありましたので、 どの要望が一番多いかを調べるべく、アンケートを作成してみることにしました。

アンケートはGoogleドライブのフォーム作成機能を使って実現しています。 簡単に作れて即公開できるのが便利ですね。

フォーム – ドライブ ヘルプ を見れば、大体の作り方は分かるかと思いますので、 アンケートを作成する際には、候補のひとつに入れてみてもよいかと思います。

Chromeで元タブとは異なるプロセスで新規タブを開く方法

Google Chromeは基本的に1タブごとに1プロセスを立ち上げるマルチプロセスのアーキテクチャを採用しています。 マルチプロセスであることにより、下記のメリットが生まれるわけです。

  1. ブラウザ本体のメモリ使用量肥大化を防ぐ
  2. 1つのタブがクラッシュしてもブラウザ全体に影響を及ぼさない

ただし、厳密に言うと1タブごとに1プロセス、というわけではありません。 具体的に言いますと、ある元タブからリンクをクリックした際に開かれるタブ、これは元タブと同じプロセスで 開かれます。 ですので、この2つのタブのうちどちらかがクラッシュすると、両方のタブがクラッシュすることになります。 また、この方法で開き続けると、1つのプロセスで何枚ものタブを開くことになるので、 最終的には1つのタブのプロセスがメモリ肥大化します。

個人的に、嫌なんですよね。常駐したいタブのメモリが肥大化するのって。 Twitterなどのサービスのタブを常駐させたまま各種リンクをクリックして見てた結果、 気がつけばTwitterタブを開いているプロセスのメモリが肥大化しているわけです。

という神経質な私のために、どうしたら別プロセスで新しいタブを開くのか確認しました。 結論から言いますと、「リンクを右クリックして新しいタブで開く」と新規タブが新しいプロセスで開けます。

以下、検証方法。ブラウザにはWindows版Chrome 33.0.1750.154mを使用しました。 新規タブで開くリンクに対して、下記の操作を行いました。

  1. 左クリックで開く
  2. 中央クリックで開く
  3. 右クリックでコンテキストメニューを出し、「新しいタブで開く」を使って開く

結果は以下のとおり、3.の右クリックして新しいタブで開く方法だけが、新しいプロセスで新規タブを 開くことが出来ました。

3つの方法で新規タブを開いた場合のプロセス

最近のマシンではそれほどメモリ使用量を気にすることはないかと思いますが、 私のような神経質さを持つ方には、是非右クリック→新しいタブで開く、をおすすめします。

今週はまともにブログを書けなかった…(読む価値なし日記)

今週も色々ありました…ちょっとここには書きにくいので、またあちこちで流すかもしれませんし、 流さないかもしれません。

とりあえずこんな一文でも、一応更新したんだぞ、と自分に言い聞かせるための 免罪符的読む価値なし日記でした。ちゃんちゃん。

Java 8リリース & java.util.streamのお勉強

ようやくJava 8がリリースされましたね。 正直Java飽きたと思っていたのですが、Java 8はかなり熱い機能が満載です。 特に関数型ちっくに書けるのは大きいですね。

とりあえず練習のため、1から100までに対するFizzBuzzを、今時点で知る限りの Java 8の機能を使って書いてみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.stream.IntStream;

public class Main {
  public static String fizzbuzz(int x) {
      if (x % 15 == 0)
          return "FizzBuzz";
      else if (x % 3 == 0)
          return "Fizz";
      else if (x % 5 == 0)
          return "Buzz";
      else
          return String.valueOf(x);
  }

  public static void p(String fb) {
      System.out.println(fb);
  }

  public static void main(String[] args) {
      IntStream.range(1, 101).boxed().map(Main::fizzbuzz).forEach(Main::p);
  }
}

もはや今までに見てきたJavaとは思えない書き方ですね。 特にmain()部分は1行で色々やっているので、解説を書いていきますが、 その前に、そもそもStreamってなに?ということで、Streamについて調べました。 以下、java.util.stream.Streamインターフェースの最初の一文の引用です。

A sequence of elements supporting sequential and parallel aggregate operations.

つまり、直列処理も並列処理も行うことができる、連続したデータを集めたもの、のようです。 今までのコレクションとの最大の違いは、並列処理も行うことができる、という部分でしょうね。 また、関数型言語のような高階関数を多数用意しているところも特徴です。

Streamが何となく雰囲気分かった気がしたところで、では1行ずつ見てみましょう。 ちなみに、「模様です」と書いている部分は、まだ私自身の調査が及んでない部分です。

1
IntStream.range(1, 101)

IntStreamはprimitiveなint専用のStreamに関するインターフェース。 IntStream.range(1, 101)により、1から100までのStreamを生成します。 (IntStream.rangeClosed(1, 100)でもOKです)

1
2
IntStream.range(1, 101)
  .boxed()

.boxed()でprimitiveなintのStreamをStream<Integer>に変換。 Pipelineにより、Stream内のデータが必要になった時点で変換する模様です。 要するに一括変換ではない模様。

1
2
IntStream.range(1, 101).boxed()
  .map(Main::fizzbuzz)

map()にはFunctionを渡します。 Function<T,R>は、型Tの入力を受け取り型Rの結果を返す関数インターフェース。 map()は、型TのStreamを、引数に渡した関数を用いて型Rのストリームを変換(生成)します。

今回map()に渡すFunctionはMainクラスのfizzbuzzメソッド。 staticメソッドに対しては、特別にMain::fizzbuzzと書くことができるようになりました。 この書き方をすることで、fizzbuzzメソッドはFunctionの匿名クラスに変換される模様です。

map()の結果として、Stream<String>で、中身はFizzBuzzのStreamが生成されたことになります。

1
2
IntStream.range(1, 101).boxed().map(Main::fizzbuzz)
  .forEach(Main::p);

forEach()にはConsumerを渡します。 Consumer<T>は型Tの入力を受け取り何も返さない手続き(何も返さないから関数ではない)インターフェース。 forEach()は、型TのStreamを用いて何かしらの処理をしますが、結果は返しません。 今回のように、標準出力などの副作用を行う場合に使うことになるかと思います。

map()が返した型はStream<String>なので、Main::pの引数はStringである必要があります。 うっかりMain::pの引数をIntegerなどStringと互換のない型にした場合、 型が違うとコンパイルエラーを返してくれるため、型で悩むことがなくなります。

(System.out::println と書いてもOKですが、型の説明のためにわざわざMain::pを定義しました)。

これでmain文の難解な1行を読み解くことが出来ました(消化不良もあるかと思いますが…)。

ちなみに、Streamの説明のところで、並列処理もOKという話をしましたが、 このFizzBuzzを並列処理しようとした場合、下記のように書けばOKです。

1
2
3
IntStream.range(1, 101).boxed()
  .paraparallel()   // ここで並列処理化している
  .map(Main::fizzbuzz).forEach(Main::p);

直列処理の場合は、1, 2, Fizz, 4, …と表示されていたのですが、 並列処理にすることにより、順番がバラバラになるのが分かるかと思います。 これはmap()が、その前のStreamが並列化されたことに伴い、 順不同で並列にデータが来るようになったためと考えられます。

結果を集計するような場合にはparallelは強力ですが、順序が重要な場合には parallelは使えないと考えたほうがよいかもしれません。 ちなみに集計には、Streamにcollect(), min(), max()が定義されていました。 最大値や最小値を、複数CPUを使って探してくれるのは、何か胸が熱くなりますね(?)。

さて、いつから実戦投入できるようになるかな?それが一番の問題ですね!

Pythonのインスタンスメソッドと関数実行

3.データモデルのうち「インスタンスメソッド」を読んでいたのですが、興味深い一文を見つけました。

インスタンスメソッドオブジェクトが呼び出される際、根底にある関数 (func) が呼び出されます。このとき、クラスインスタンス (self) が引数リストの先頭に挿入されます。例えば、 C を関数 f() の定義を含むクラス、 x を C のインスタンスとすると、 x.f(1) の呼び出しは C.f(x, 1) の呼び出しと同じです。

これを使うと、あるクラスのインスタンスメソッドを列挙しておいて、 リフレクションのように順次メソッドを実行しておくことが簡単に、かつオーバーヘッドがない状態で 実行することができる模様です。

こういう具合に書くと、

1
2
3
4
5
6
ops = [Puzzle.up, Puzzle.down]

p = Puzzle()

for op in ops:
    op(p)

こういう書き方をしたときと同じ挙動になるわけです。

1
2
3
p = Puzzle()
p.up()
p.down()

行数は後者の方が少ないですが、前者の書き方が必要なときもあるんですよ。 特にメソッドをひたすら実行しまくる場合には、前者の書き方は便利です。 しかもJavaでリフレクションを使った時のようなオーバーヘッドがないのは良いですね (普通Javaではこういう書き方せずにインターフェースとか使うけど)。

クラスでメソッド定義をするときに、def up(self): と書くのは、 インスタンスメソッドとして使った場合には、第1引数のselfにはオブジェクトそのものを勝手に 指定してくれるような動きになっているのかな?

追記 (2014-03-19)

メソッドはレシーバを第1引数に受け取るただの関数、ということだそうです。

大阪Pythonユーザの集まり2014/03に参加&発表してきた ‘#osakapy

まえがき

久々の勉強会企画である大阪Pythonユーザの集まり2014/03に 行ってきて、ついでに発表もしてきました。 勉強会でまともな話を発表するのは初めてで、ものすごく緊張しましたが、 なんとか生きて帰ってこれました。

会場をご提供いただきましたシナジーマーケティングさんと@kawakenさん、 ありがとうございます! 増田さん、司会進行ありがとうございます! そして、お足元の悪い中ご参加いただきました皆様、ありがとうございます!

発表内容

scikit-learnを用いた機械学習チュートリアル

私の発表です。 機械学習を使う上での取っ掛かりの考え方、それを実際にscikit-learnで実装するには どうすればよいかを、テキスト分類の例を交えながら説明したつもりです。

wxPython入門

増田さん(@whosaysni)によるwxPythonのお話。 クロスプラットフォームで各OSのネイティブなコンポーネントを使うあたり、 Swingより好感が持てる(JavaFXはすみませんが不勉強で…)。

あとで調べてみましたが、Python 3対応はまだみたいですね。

Autodocについて

https://speakerdeck.com/heavenshell/autodoc

@heavenshell さんによる、Ruby発 Autodoc – r7kamura per secondの Python版を作った、というお話。 Web APIサーバーに対するテストを書くと、テストからドキュメントが自動生成されるとのこと。

docstringを使うという選択肢もあったが、的な設計思想に関する質疑応答も聞けて Pythonな人の考え方がかいま見えた気がしました。

「我々Pythonistaは〜」というフレーズを何回か聞きました。 Pythonの勉強会に参加するのは初めてなので、Pythonはそういうノリなのかしら?

kurokoについて (LT)

kurokoについて (2014-03-17 URL追加)

@hhatto さんによる、定期実行するタスクを扱うための kurokoについてのLT。

デコレーターいいね、な人が今日の勉強会で2名。 まだデコレーターを使ったことがないので、使ってみようっと。

ちょっとした黒魔術でライブラリのバグを回避した話 (LT)

@soundkitchen さんによる、Pythonの黒魔術のお話。 使ってるライブラリのコンストラクタ周りにバグがあったので コンストラクタを差し替えたという荒業のお話をしてくれました。

Pythonは黒魔術的な実装はあまり好かれないそうですが、 それでも最終手段としてこういうことができる余地を残してくれているのは素晴らしい、 とのことです。

懇親会

懇親会は9名、うち7名が、本勉強会を企画するに当って集まっていただいた方々でした。 個人的には、懇親会までが勉強会だと思っていますので(ブログを書くまでが勉強会、という話もあるけど)、 面白い話をもっと聞きたければ懇親会に参加すべきですよ〜と思っています。

今回懇親会に参加されなかった方々も、次回はご参加ください。 前の会社の後輩にも懇親会に参加していただきましたが、色々楽しい話が聞けてよかった、 と言ってくれていました。

感想、反省点など

  • 事前の準備は周囲の協力をいただきながら、ぼちぼちの出来だったかな?
  • 一方、懇親会の案内はもっと早くに出すべきだった
  • 肝心の発表は反省点だらけ。もっと練習しなきゃいけなかったし、スライドも洗練させるべきだった

今後

関西Pythonのメーリングリストあたりで相談しながら、次回を決めていく予定です。 私も企画に絡んでいきます。 ハッカソンしたいなぁ。Pythonの開発をもう少しやってみたいし、 開発スタイルが多分オレオレ感満載なことになっていると思うので…。