蛇ノ目の記

技術のことも。そうでないことも。

PyCon JP 2018とBP入社一周年

PyCon JP 2018

9/15~9/18にかけてPyCon JP 2018が開催された。

pycon.jp

去年に引き続きスタッフとしての参加。今年はコンテンツチームに所属して、トークの採択やコミュニティブースの企画に関わった。去年作った領収書発行サイトの更新もやったりした(デプロイはシステムチームの方にお願いしたのだけど)。

コミュニティブースはPythonコミュニティが活動を紹介する企画で、南はPyCon Kyushu、北は札幌Pythonまで日本各地から6つのコミュニティが参加した。

コンテンツチームのタスクにはトークセッションの進行も含まれている。そういうわけで今年はあまりトークが聴けていない。とはいえ、いい感じにシフトを組んでくれたおかげで聴きたいトークの進行に割り当てられることもあった。以下、進行をしながら聴いたトーク

Day1

  • Webアプリケーションの仕組み - Takayuki Shimizukawa

  • あなたと私いますぐパッケージン - Atsushi Odagiri

  • Interactive Network Visualization using Python 〜 NetworkX + BokehでPEPの参照関係を可視化する - Tomoko Furuki

  • How to Data Wrangling? Tips for using python libraries for big-data analysis including scikit-learn. - 松岡光

Day2

  • HomeSecurity with Python - Yuki Takino

  • 複数アプリケーションのプロセスとログを管理するための新しいツールと手法- 谷津真樹/Masaki Yatsu

  • From Data to Web Application: Anime Character Image Recognition with Transfer Learning - Iskandar Setiadi

  • Django を Zappaで構築してServerless Python のベストプラクティスを探る - 向山 裕介 (Yusuke Mukoyama)

実のところ、来年は一般枠で参加しようと思っていたけどコンテンツチームをやってみて、改善したいことや新しくやってみたいことが少し出てきたので来年もまたスタッフTシャツを着ていそうな気がする。

BP入社一周年

PyCon JP 2018のカンファレンス前日準備の9/16がビープラウド入社一周年だった。

nao-y.hatenablog.com

この記事にある通り、3連休だったので初出社したのは9/19だったのだけど。

この1年でPythonistaとしてどれだけ成長できたのか、あまり自分ではわかっていない。仕事でPythonを使ったことがなかった人間がconnpassやPyQ、受託案件に関わってなんとかやってこれたのでは多少なりとも成長できたのだと思いたい。

入社した頃はまだ手が届かなかったと思っていた書籍執筆はSoftware Design9月号の特集記事の一つを書くという形で体験できた。書籍レビュー・監修に至っては「Pythonプロフェッショナルプログラミング第3版」「スラスラ読める Pythonふりがなプログラミング」「Pythonで学ぶあたらしいデータ分析の教科書」と3冊も関わることができた。

うまい具合に機会を拾ってやってこれた。あれ、それなりに成長してるのでは。とはいえWebもデータも全然まだまだなのでやれること・やりたいことをきちんとやっていきたい。

ただただエモいだけの話になってしまったが、次の1年もBPで頑張っていく。

Python3.7でDiscord.pyを動かすときの躓きどころと対策

近頃、またDiscord botを作っている。

Python.3.7に上げたことによってDiscord.pyが動かないという現象に見舞われたので対策をメモ。

発生したエラー

このissueと同様のエラーが発生した。

github.com

対策

This library does not support 3.7. This error in particular is caused by async becoming a reserved keyword.

Please use Python 3.4-3.6.

async予約語になったからっぽい。Python3.4-3.6を使って、どうぞ。」

Python3.7からasync予約語になったことが原因のようだ。

バージョンを下げたくなかったのでさらに読み進めてみた。

https://github.com/Rapptz/discord.py/issues/1249#issuecomment-412256277

によれば

Your options are the following:

  • separately upgrade aiohttp and websockets to the latest versions after installing the async branch from GitHub
  • downgrade to Python 3.6
  • switch to the rewrite branch
  • aiohttpとwebsocketsを別々にアップグレードしてから、asyncブランチをインストールする

  • Python3.6にダウングレードする

  • rewriteブランチを使う

とのこと。一番手っ取り早そうなrewriteブランチを使う方法でやってみる。

ちなみに、rewriteブランチを使うという対策は以下のQiitaでも紹介されている(エラーの原因については触れられていない)。

qiita.com

以下のコマンドでDiscord.pyのrewriteブランチをインストール。

pip install git+https://github.com/Rapptz/discord.py.git@rewrite

rewriteブランチでの注意点

これまではbotに発言させる際にclient.send_messageメソッドを使っていたが、rewriteブランチではmessage.channel.sendメソッドを使う。ググって出てくるDiscord.pyの使い方は前者で書かれていることが多いので気をつけよう。

Software Design2018年9月号にて「データ分析にPythonが選ばれる理由」を執筆しました

というわけで記事を執筆しました。とうとう著者デビューしたので著者ページ作ってみました。著者画像も経歴もまだないけど。

Amazon.co.jp: 横山 直敬:作品一覧、著者略歴

さて、この記事はPython初学者向けを意識して、以下の4つに焦点を当てています。

  • データ分析におけるPythonの強み

  • データ分析のための環境構築

  • 手軽に入手できるデータセットの紹介

  • ボストン市地域別平均住宅価格データの分析を体験

データ分析といえばAnacondaが便利ですが、condaとpipが混在しているせいでハンズオンで躓いたという事例をよく聞くので、Anacondaを使わずPython3.6.6とvenvを使った環境構築を説明しました(執筆中に3.6.6と3.7が出たときは少し焦った)。

データセット紹介ではscikit-learn付属データセットとKaggle Datasetsについて触れています。データ分析体験では、線形回帰を使って住宅の部屋数から住宅価格を予測します。

Pythonによるデータ分析を手軽に体験できる記事に仕上がっていると思います。ぜひお手にとってみてください。

Special Thanks

  • レビューしてくれたBPのみなさん

  • 技術評論社の編集者さん

  • 告知ツイートをRT,いいねしてくれた人たち

  • これから雑誌を買ってくれる・買ってくれた人たち

tagmineをリリースしました-酷暑のイベントの緊急時に役立つWebアプリ

tagmineをリリースしました。

f:id:Nao_Y:20180809010509p:plain

tagmineとは

熱中症などで倒れたとき、医療処置に役立つ情報を埋め込んだQRコードを作成するWebアプリです。

QRコードに埋め込める主な情報

  • 氏名
  • 血液型
  • 既往歴
  • 緊急連絡先

QRコードは名刺サイズのカードとして表示されるので、印刷して使うことができます。財布の中やネックストラップの内側に入れるといざというときに見つけてもらいやすいでしょう。

少し前にこんなtogetterまとめを見て、QRコード版ドッグタグを作ってみようと思ったのがきっかけです。

togetter.com

コミケが8/10から始まります。今年も暑くなりそうですので、活用してもらえたら嬉しいです。

tagmineの中身の話はまた後日。

herokuのProcfileに書くgunicornのコマンドにハマった話

herokuで動かすWebアプリがどんなプロセスを使うかを定義するProcfile

Procfileはドキュメントルートに置き、例えばFlask + gunicornを使うときには web: gunicorn app:appとする。古事記にもそう書いてある(heroku公式ドキュメントやいろいろなブログ)

heroku公式ドキュメント: Deploying Python Applications with Gunicorn | Heroku Dev Center

その教えに従ってProcfileを作成したが一向に動かない。

Failed to find application object 'app' in 'myapp'

と表示されるばかり。

ディレクトリ構成はこんな感じ。

flask_app
├ Procfile
├ requirements.txt
├ runtime.txt
└ myapp
  ├ myapp.py
  ├ static
  └ templates

一度myappディレクトリに移動する必要があるのでは、と考えて

web: gunicorn --chdir myapp myapp:app

と書き換えたところアプリが正常に動いた。

作ったWebアプリの話はまた後日。

追記

アプリのディレクトリを分けずに以下のようなディレクトリ構成にすることで、Procfileにディレクトリ移動を書かなくてよくなる。アプリのディレクトリを分けているとローカルでの実行やパッケージングで不便になる、と@shimizukawaからアドバイスをもらった。ありがとうございます。

flask_app
├ Procfile
├ requirements.txt
├ runtime.txt
├ myapp.py
├ static
├ templates

また、herokuの環境変数PYTHONPATHをPYTHONPATH=myappsと設定すると、アプリのディレクトリを分けていてもProcfile実行時のディレクトリ移動が必要なくなる。と@shimizukawaからアドバイスをもらった。ありがとうございます。

ユニットテストで躓いたところ - mock.patch()

単純なスクリプトユニットテストを書いていて躓くことがあったので、解決法をメモ。

例えばこんな、おみくじをするだけの簡単なスクリプトがあるとする。

import random


fortunes = {
    1: '凶',
    2: '吉',
    3: '大吉'
}

number = random.choice([1, 2, 3])

fortune = fortunes[number]

print(fortune)

変数numberに1〜3までのランダムな値が入って、対応する運勢が出力される 。random.choice()の部分をパッチして、期待通りの運勢が出力されることをテストする。

import unittest

from importlib import import_module
from unittest.mock import patch


class FortuneTestCase(unittest.TestCase):

    @patch('random.choice', lambda x: 1)
    def test_bad(self):
        module = import_module('fortune')
        expect = ('凶')
        self.assertEqual(module.fortune, expect)

    @patch('random.choice', lambda x: 3)
    def test_good(self):
        module = import_module('fortune')
        expect = ('大吉')
        self.assertEqual(module.fortune, expect)

さて、このテストを実行するとどうなるか。

(env) $ python3 -m unittest tests.test
凶
.F
======================================================================
FAIL: test_good (tests.test.FortuneTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py", line 1191, in patched
    return func(*args, **keywargs)
  File "/tests/test.py", line 21, in test_good
    self.assertEqual(module.fortune, expect)
AssertionError: '凶' != '大吉'
- 凶
+ 大吉


----------------------------------------------------------------------
Ran 2 tests in 0.029s

FAILED (failures=1)

test_goodメソッドのアサーションで例外が発生する。パッチした値が反映されていない。つまり初めに実行されたtest_badメソッドでパッチした値がそのまま使われている。

値がパッチされっぱなしなのではれば、モジュールを読み込み直してみる。importlib.reload()を使う。

Python3 公式ドキュメント importlib - reload

import unittest

from importlib import import_module, reload
from unittest.mock import patch


class FortuneTestCase(unittest.TestCase):

    @patch('random.choice', lambda x: 1)
    def test_bad(self):
        module = import_module('fortune')
        expect = ('凶')
        self.assertEqual(module.fortune, expect)

    @patch('random.choice', lambda x: 3)
    def test_good(self):
        module = import_module('fortune')
        reload(module)
        expect = ('大吉')
        self.assertEqual(module.fortune, expect)
(env) $ python3 -m unittest tests.test
凶
凶
.大吉
.
----------------------------------------------------------------------
Ran 2 tests in 0.025s

OK

無事テストが通った。テストメソッド実行ごとにパッチされると思っていただけにこの動きにだいぶ悩むこととなった。

公式ドキュメントに以下の注釈があるためtest_goodメソッドでだけreloadを使った。

注釈 :いろいろなテストが実行される順序は、文字列の組み込みの順序でテストメソッド名をソートすることで決まります。

https://docs.python.jp/3/library/unittest.html#organizing-test-code

BokehでグラフをJavaScriptとして出力してWebページに埋め込んでみた

グラフをWebページに埋め込んで、インタラクティブなWebアプリっぽくしたい。でもJavaScriptわからない。そんなときにBokehのembed.componentsを使おう。

Bokehのバージョンは0.13.0を使っている。

bokeh.pydata.org

componentsに描画するグラフのFigureオブジェクトを渡すと、JavaScriptを記述したscriptタグとグラフ部分のdivタグがタプルとして返ってくる。

scirpt, div= components(plot)

手始めにBokehのQuickstartにある折れ線グラフを表示してみる。

bokeh.pydata.org

from bokeh.embed import components
from bokeh.plotting import figure
from flask import Flask, render_template

app = Flask(__name__)


def get_line_graph():
    # prepare some data
    x = [1, 2, 3, 4, 5]
    y = [6, 7, 2, 4, 5]

    # create a new plot with a title and axis labels
    p = figure(title="simple line example", x_axis_label='x', y_axis_label='y')

    # add a line renderer with legend and line thickness
    p.line(x, y, legend="Temp.", line_width=2)
    print(type(p))

    return p

@app.route('/')
def index():
    plot = get_line_graph()
    script, div = components(plot)
    return render_template('index.html', script=script, div=div)
<!DOCTYPE html>
<html>
<head>
    <title>Flask Bokeh Sample</title>
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.13.0.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-tables-0.13.0.min.css" type="text/css">

    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-api-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-tables-0.13.0.min.js"></script>
</head>

<body>
    <h1>Hello, Bokeh</h1>
    {{ script|safe }}
    {{ div|safe }}
</body>

</html>

Jinja2safeフィルタを使うことで、Flaskから渡した文字列をHTMLとして埋め込むことができる。テンプレートで気をつけておきたいのはCSSJavaScriptCDNから読み込むときのバージョン。Bokehの最新バージョンに合わせておこう。ここでは0.13.0としている。

このStackOverFlowの回答を参考にしたが、動かなかったので試しにバージョンを最新にしたら動いた。

stackoverflow.com

さて、上記のコードを実行すると以下のようなWebページになる。動かせるし、ズームも保存もできる。

f:id:Nao_Y:20180716104814p:plain

複数のグラフを埋め込むときはcomponents()メソッドにFigureオブジェクトが入ったタプルを渡す。すると、divタグがタプルとして返ってくる。

以下、折れ線グラフに加えて散布図を埋め込んだ例。

from bokeh.embed import components
from bokeh.plotting import figure
from flask import Flask, render_template
import numpy as np

app = Flask(__name__)


def get_line_graph():
    # prepare some data
    x = [1, 2, 3, 4, 5]
    y = [6, 7, 2, 4, 5]

    # create a new plot with a title and axis labels
    p = figure(title="simple line example", x_axis_label='x', y_axis_label='y')

    # add a line renderer with legend and line thickness
    p.line(x, y, legend="Temp.", line_width=2)
    print(type(p))

    return p

def get_scatter():
    # prepare some data
    N = 4000
    x = np.random.random(size=N) * 100
    y = np.random.random(size=N) * 100
    radii = np.random.random(size=N) * 1.5
    colors = [
        "#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50 + 2 * x, 30 + 2 * y)
    ]

    TOOLS = "crosshair,pan,wheel_zoom,box_zoom,reset,box_select,lasso_select"

    # create a new plot with the tools above, and explicit ranges
    p = figure(tools=TOOLS, x_range=(0, 100), y_range=(0, 100))

    # add a circle renderer with vectorized colors and sizes
    p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)

    return p


@app.route('/')
def index():
    line = get_line_graph()
    scatter = get_scatter()
    plots = (line, scatter)
    script, div = components(plots)
    return render_template('index.html', script=script, div=div)
<!DOCTYPE html>
<html>
<head>
    <title>Flask Bokeh Sample</title>
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.13.0.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.pydata.org/bokeh/release/bokeh-tables-0.13.0.min.css" type="text/css">

    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-api-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.13.0.min.js"></script>
    <script type="text/javascript" src="https://cdn.pydata.org/bokeh/release/bokeh-tables-0.13.0.min.js"></script>
</head>

<body>
    <h1>Hello, Bokeh</h1>
    {{ script|safe }}
    {{ div.0|safe }}
    {{ div.1|safe }}
</body>

</html>

f:id:Nao_Y:20180716105326p:plain

以上、BokehのグラフをJavaScriptにしてWebページに埋め込む方法。

BokehのWidgetsを使うとダッシュボードめいたやつとか作れそう。

bokeh.pydata.org