蛇ノ目の記

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

プログラミングの面白さを感じながらITエンジニア目指してほしいよねっていうエモい話

ここ数年でプログラミングを学ぶハードルは格段に下がった。無料の動画コンテンツはたくさんあるし、少しお金を出せばUdemyなんかでさらに踏み込んだ内容を学ぶことだってできる。あとは実際に手を動かして学べるコンテンツも充実してきている。

「ITエンジニアになれば給料が上がる」とか「未経験でもITエンジニアになれる」という言葉も目にするようになってきた。

そんな中でおととい、Twitterのタイムラインでこのブログ記事を目にした。この1件だけでこういうことを言うのは憚れるけど、「未経験でもITエンジニアになれる」という向きには違和感を覚える。

30sman.com

プログラミングスクール上がりの新人の教育にとても苦労したというSEの体験談なのだけど、この新人が悪いとかプログラミングスクールが悪いとか言うつもりはなくて、ただ「仕事に就く」という目的だけで、それ以外のモチベーションが無いまま勉強していたがために起きた不幸なことなのかなと思った。

未経験の状態からでも十分な教育を受ければ実践で活躍できるようになれると信じているけど、なによりその前にプログラミングを面白いと感じられるかどうかが重要。プログラミングを教える側としては面白さに気づける仕組みやコンテンツを作りたい。一方、ITエンジニアを目指して勉強する側は「仕事に就く」ことだけにとらわれずに、プログラミングでできるいろいろな面白いことにも目を向けて、いざ仕事に就いたときに楽しくやれるようになってほしい。

周りが猛者ばかりだと生存者バイアスみたいなもので気づきにくいけど、プログラミングを面白いと感じられない人はいるはず。結構大変な話だけど、教える側はそこに気づいたら、フォローしたり、時にはITエンジニア以外の道を示すこともあっていいのかなと思う。そもそも面白く感じない人がなんでITエンジニア目指すんだよってなるけど、「未経験でも」っていう発想だとどうせ仕事だし、ってことでまあまあありそう。

ちなみに「未経験でもITエンジニアになれる」という言い方は仕事を下に見ているような感じがしてあまり好きじゃない。「未経験だけどプログラミングって面白そうだからITエンジニアになりたい」と思う人が増えたらいいなと願っている。あ、給料が上がるらしいっていうのはモチベーションがぶち上がっていいと思います。

Dockerコンテナ上でPythonスクリプトを動かしてみる

仕事でDjango製のWebアプリのテスト環境をDockerで作る、という試みをやったのでその入り口になる話を書いてみる。

Docker Hubを探せばPython3.7のイメージはあるけど、ここではUbuntu16.04のイメージを使う。

Ubuntu 16.04のイメージにPython3.7をインストールする

Dockerfile中にコメントを書いている通り、Ubuntu16.04にPython3.7をインストールする方法は以下の記事を参考にした。

medium.com

# Dockerfile
FROM ubuntu:16.04

# refs. Install Python3.7 in ubuntu 16.04
# https://medium.com/@manivannan_data/install-python3-7-in-ubuntu-16-04-dfd9b4f11e5c
RUN apt update && apt-get install -y \
    build-essential \
    checkinstall \
    libreadline-gplv2-dev \
    libncursesw5-dev \
    libssl-dev \
    libsqlite3-dev \
    tk-dev \
    libgdbm-dev \
    libc6-dev \
    libbz2-dev \
    zlib1g-dev \
    openssl \
    libffi-dev \
    python3-dev \
    python3-setuptools \
    wget \
    && mkdir /tmp/Python37
WORKDIR tmp/Python37
RUN wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz \
    && tar xvf Python-3.7.0.tar.xz
WORKDIR /tmp/Python37/Python-3.7.0
RUN ./configure --enable-optimizations \
    && make altinstall \
    && mkdir /usr/local/Python
WORKDIR /usr/local/Python

Linux力が全然無いので、Python3.7を入れるだけでこんなにパッケージが必要なのか(小並)となった。

docker-compose.ymlを書く

次にdokcer-compose.ymlを用意する。

Pythonスクリプトを置いたディレクトリをReadOnlyでマウントしてから、ファイルを実行する。

# docker-compose.yml
version: "2"
services:
  python:
    build: .
    container_name: run_python
    volumes:
      - ./src:/usr/local/Python/:ro 
    command: "python3.7 sample.py"

以下がそのスクリプト

# sample.py
print('spam ham eggs')

マウントしたスクリプトを実行する

docker-compose build でサービスをビルドしてから、docker-compose upでサービスを実行する。

スクリプトがファイルを出力するとき

docker-compose.ymlの中でマウントすると、コンテナ上で作成されたファイルはローカルにも作られる。

このとき、ReadOnlyを外しておかないと怒られる。

# sample.py
zen_en = 'zen_en.txt'
zen_ja = 'zen_ja.txt'

with open(zen_en, 'w', encoding='utf-8') as f:
    f.write('Beautiful is better than ugly.')

with open(zen_ja, 'w', encoding='utf-8') as f:
    f.write('醜いよりも美しい方が良い。')

これを書いて、サービスを実行するとローカルに2つのファイルが作られる。

ローカルに影響を与えたくないのであれば、DockerfileCOPYを追加する。

このとき、docker-compose.ymlからvolumesは消しておく。

# Dockerfile
.
.
.
WORKDIR /tmp/Python37/Python-3.7.0
RUN ./configure --enable-optimizations \
    && make altinstall \
    && mkdir /usr/local/Python
WORKDIR /usr/local/Python
COPY ./src /usr/local/Python/
# docker-compose.yml
version: "2"
services:
  python:
    build: .
    container_name: run_python
    command: "python3.7 sample.py"

スクリプトに変更があったときはサービスをビルドし直してから、実行する必要があってひと手間増えてしまう(´・ω・`)

sample.pyにコンテナ上にファイルが作られたか確認するためにファイルの中身を確認する処理を追記した。

# sample.py
zen_en = 'zen_en.txt'
zen_ja = 'zen_ja.txt'

with open(zen_en, 'w', encoding='utf-8') as f:
    f.write('Beautiful is better than ugly.')

with open(zen_ja, 'w', encoding='utf-8') as f:
    f.write('醜いよりも美しい方が良い。')

with open(zen_en, encoding='utf-8') as f:
    for line in f:
        print(line)

with open(zen_ja, encoding='utf-8') as f:
    for line in f:
        print(line)
$ docker-compose build
.
.
.
$ docker-compose up
Recreating run_python ... done
Attaching to run_python
run_python | Beautiful is better than ugly.
run_python | 醜いよりも美しい方が良い。
run_python exited with code 0

まとめ

「Dockerコンテナ上にスクリプトを置いて、コマンドを実行する」という流れを応用して、「Webアプリを置いてテストコマンドを実行」とすればDockerを使ったテスト環境を作れる。

PythonでQRコードを生成してXMLに変換する

PythonQRコードを生成してXMLに変換する話。

qrcodeというパッケージを使う。

pypi.org

8月半ばに作ったWebアプリでXMLに変換したQRコードを使っている。

nao-y.hatenablog.com

QRコードSVGを生成する話は以下のブログが詳しい。

tokibito.hatenablog.com

今回はさらに、SVGXMLにタグに変換してHTMLに埋め込むところまでをやっていく。

コードは以下のGistで公開している。

PythonでQRコードを生成してSVGタグに変換する · GitHub

全体の流れ

def main():
    # QRコードに埋め込むテキストの読み込み
    with open('zen.txt', encoding='utf-8') as f:
        text = f.read()
    # QRコードのSVG作成
    qr = make_qrcode_svg(text)
    # DOMオブジェクトを作成
    dom = create_dom(qr)
    # DOMをXMLに整形
    xml = dom.toprettyxml()

    HTML = f'''
<!DOCTYPE html>
<head>
<title>sample QR</title>
</head>
<body>
{xml}
</body>
</html>
'''
    # HTMLに埋め込み
    with open('sample.html', 'w', encoding='utf-8') as f:
        f.write(HTML)

QRコードSVGを作る

import xml.dom.minidom

import qrcode
import qrcode.image.svg as svg


def make_qrcode_svg(text):
    """
    QRコードのSVGを作る関数
    :param text: QRコードに埋め込みたい文字列
    :return: QRコードのSVG
    """
    factory = svg.SvgPathImage
    img = qrcode.make(
        text,
        image_factory=factory,
        version=1,
        box_size=6,
        error_correction=qrcode.constants.ERROR_CORRECT_L)
    return img

XMLの構造

具体的にXMLにする方法を説明する前に、XMLの構造を簡単にまとめる。

以下が今回、生成するXML

<?xml version="1.0" ?>
<svg height="21mm" viewBox="0 0 21 21" width="21mm" xmlns="http://www.w3.org/2000/svg">
    <path d=". . .">
</svg>

初めにXML宣言がある。<?xml version="1.0" ?> の部分。バージョンや文字コードを定義する。

XMLにおいてタグは要素と呼ぶ(要素ノードとも呼ばれる)。

要素には属性を設定できる(属性もまた、属性ノードとも呼ばれる)。

svg要素のheight属性、というような感じ。

また要素には子要素を設定できる。path要素がsvg要素の子要素にあたる。

XMLを生成する

標準ライブラリのxml.dom.minidomを使う。以下のQiita記事を参考にした。

qiita.com

def create_dom(img):
    """
    QRコードのsvgをXMLとして出力する関数
    :param img: QRコードのsvg
    :return dom: xml.dom.minidom.Document Object
    """
    width = img.width
    items = img.get_image()
    path = img.make_path()

    # DOM
    dom = xml.dom.minidom.Document()
    # svg要素を作成
    elem = dom.createElement('svg')
    # DOMに子要素elemを追加
    dom.appendChild(elem)

    # svgからxmlns属性を取得
    xmlns = items.get('xmlns')

    # svg要素の属性を指定
    values = {
        'width': f'{width}mm',
        'height': f'{width}mm',
        'viewBox': f'0 0 {width} {width}',
        'xmlns': xmlns
    }

    # DOMにsvg要素を設定
    set_dom_attr(dom, elem, values)

    # path要素を作成
    path_node = dom.createElement('path')
    # elem要素に子要素(path_node)を追加
    elem.appendChild(path_node)

    # pathからd属性を取得
    d = path.get('d')
    # pathからid属性を取得
    id_ = path.get('id')
    # pathからstyle属性を取得
    style = path.get('style')

    # path要素の属性を指定
    values = {
        'd': d,
        'id': id_,
        'style': style,
    }

    # DOMにpath要素を設定
    set_dom_attr(dom, path_node, values)
    return dom
def set_dom_attr(dom, elem, values):
    """
    属性ノードを生成して要素を設定する関数
    :param dom: xml.dom.minidom.Document Object
    :param elem: 要素
    :param values: 属性
    :return:
    """
    for attr, value in values.items():
        # 属性ノードを生成
        subnode_attrs = dom.createAttribute(attr)
        # 属性ノードの値を設定
        subnode_attrs.value = value
        # 属性ノードを要素ノードにセットする
        elem.setAttributeNode(subnode_attrs)

以下のようにHTMLが出力される(path要素の中身は長すぎるので省略。

<!DOCTYPE html>
<head>
<title>sample QR</title>
</head>
<body>
<?xml version="1.0" ?>
<svg height="21mm" viewBox="0 0 21 21" width="21mm" xmlns="http://www.w3.org/2000/svg">
    <path d=". . .">
</svg>
</body>
</html>

出力されたHTMLは以下のようになる。 f:id:Nao_Y:20180923203817p:plain

Webアプリで使う場合はこのQRコードの部分(今回はxml変数)にテンプレートエンジンのフィルター(Jinja2ではsafeフィルター)を掛ければHTML埋め込むことができる。

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の中身の話はまた後日。